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