Interactive online tutorial: Notebook

Working with Metadata

LibCST handles node metadata in a somewhat unusual manner in order to maintain the immutability of the tree. See Metadata for the complete documentation.

Providing Metadata

While it’s possible to write visitors that gather metadata from a tree ad hoc, using the provider interface gives you the advantage of being able to use dependency declaration to automatically run your providers in other visitors and type safety. For most cases, you’ll want to extend BatchableMetadataProvider as providers that extend from that class can be resolved more efficiently in batches.

Here’s an example of a simple metadata provider that marks Name nodes that are function parameters:

[2]:
import libcst as cst


class IsParamProvider(cst.BatchableMetadataProvider[bool]):
    """
    Marks Name nodes found as a parameter to a function.
    """
    def __init__(self) -> None:
        super().__init__()
        self.is_param = False

    def visit_Param(self, node: cst.Param) -> None:
        # Mark the child Name node as a parameter
        self.set_metadata(node.name, True)

    def visit_Name(self, node: cst.Name) -> None:
        # Mark all other Name nodes as not parameters
        if not self.get_metadata(type(self), node, False):
            self.set_metadata(node, False)

Line and Column Metadata

LibCST ships with two built-in providers for line and column metadata. See Position Metadata for more information.

Accessing Metadata

Once you have a provider, the metadata interface gives you two primary ways of working with your providers. The first is using the resolve methods provided by MetadataWrapper and the second is through declaring metadata dependencies on a CSTTransformer or CSTVisitor.

Using the MetadataWrapper

The metadata wrapper class provides a way to associate metadata with a module as well as a simple interface to run providers. Here’s an example of using a wrapper with the provider we just wrote:

[3]:
module = cst.parse_module("x")
wrapper = cst.MetadataWrapper(module)

isparam = wrapper.resolve(IsParamProvider)
x_name_node = wrapper.module.body[0].body[0].value

print(isparam[x_name_node])  # should print False
False

Using Dependency Declaration

The visitors that ship with LibCST can declare metadata providers as dependencies that will be run automatically when visited by a wrapper. Here is a visitor that prints all names that are function parameters.

[4]:
from libcst.metadata import PositionProvider

class ParamPrinter(cst.CSTVisitor):
    METADATA_DEPENDENCIES = (IsParamProvider, PositionProvider,)

    def visit_Name(self, node: cst.Name) -> None:
        # Only print out names that are parameters
        if self.get_metadata(IsParamProvider, node):
            pos = self.get_metadata(PositionProvider, node).start
            print(f"{node.value} found at line {pos.line}, column {pos.column}")


module = cst.parse_module("def foo(x):\n    y = 1\n    return x + y")
wrapper = cst.MetadataWrapper(module)
result = wrapper.visit(ParamPrinter())  # NB: wrapper.visit not module.visit
x found at line 1, column 8