0
# Visitor Framework
1
2
LibCST's visitor framework provides a powerful pattern for traversing and transforming concrete syntax trees. The framework supports read-only analysis, tree transformation, metadata-dependent processing, and batched operations for performance optimization.
3
4
## Capabilities
5
6
### Basic Visitor
7
8
Read-only traversal of CST trees for analysis and data collection.
9
10
```python { .api }
11
class CSTVisitor:
12
"""Base class for read-only CST traversal."""
13
14
def visit(self, node: CSTNode) -> None:
15
"""Called when entering a node during traversal."""
16
17
def leave(self, node: CSTNode) -> None:
18
"""Called when leaving a node during traversal."""
19
20
# Node-specific visit methods (examples)
21
def visit_Module(self, node: Module) -> None: ...
22
def visit_FunctionDef(self, node: FunctionDef) -> None: ...
23
def visit_Name(self, node: Name) -> None: ...
24
def visit_Call(self, node: Call) -> None: ...
25
26
# Node-specific leave methods (examples)
27
def leave_Module(self, node: Module) -> None: ...
28
def leave_FunctionDef(self, node: FunctionDef) -> None: ...
29
def leave_Name(self, node: Name) -> None: ...
30
def leave_Call(self, node: Call) -> None: ...
31
```
32
33
### Transformer
34
35
Transform CST trees by replacing nodes during traversal.
36
37
```python { .api }
38
class CSTTransformer(CSTVisitor):
39
"""Base class for CST transformation."""
40
41
def leave(self, original_node: CSTNode, updated_node: CSTNode) -> CSTNode:
42
"""
43
Transform node during traversal.
44
45
Parameters:
46
- original_node: Original node before any child transformations
47
- updated_node: Node with transformed children
48
49
Returns:
50
CSTNode: Replacement node or updated_node to keep unchanged
51
"""
52
53
# Node-specific transformation methods (examples)
54
def leave_Module(self, original_node: Module, updated_node: Module) -> Module: ...
55
def leave_FunctionDef(self, original_node: FunctionDef, updated_node: FunctionDef) -> Union[FunctionDef, RemovalSentinel]: ...
56
def leave_Name(self, original_node: Name, updated_node: Name) -> Name: ...
57
def leave_Call(self, original_node: Call, updated_node: Call) -> Union[Call, BaseExpression]: ...
58
```
59
60
### Metadata-Dependent Analysis
61
62
Access semantic information during traversal using metadata providers.
63
64
```python { .api }
65
class MetadataDependent:
66
"""Base class for visitors that depend on metadata."""
67
68
METADATA_DEPENDENCIES: Sequence[Type[BaseMetadataProvider]] = ()
69
70
def resolve(self, node: CSTNode) -> Any:
71
"""
72
Resolve metadata for a node.
73
74
Parameters:
75
- node: CST node to resolve metadata for
76
77
Returns:
78
Any: Metadata value for the node
79
"""
80
81
def resolve_many(self, providers: Sequence[Type[BaseMetadataProvider]]) -> Dict[Type[BaseMetadataProvider], Mapping[CSTNode, Any]]:
82
"""Resolve multiple metadata providers at once."""
83
```
84
85
### Batched Visitor
86
87
Process multiple nodes efficiently with batched operations.
88
89
```python { .api }
90
class BatchableCSTVisitor(CSTVisitor):
91
"""Base class for visitors that support batched processing."""
92
93
def on_visit(self, node: CSTNode) -> bool:
94
"""
95
Determine if this visitor should process the node.
96
97
Parameters:
98
- node: Node being visited
99
100
Returns:
101
bool: True if visitor should process this node type
102
"""
103
104
def on_leave(self, original_node: CSTNode) -> None:
105
"""Process node when leaving during batched traversal."""
106
107
def visit_batched(
108
nodes: Sequence[CSTNode],
109
visitors: Sequence[BatchableCSTVisitor]
110
) -> None:
111
"""
112
Visit multiple nodes with multiple visitors efficiently.
113
114
Parameters:
115
- nodes: Sequence of CST nodes to visit
116
- visitors: Sequence of visitors to apply
117
"""
118
```
119
120
### Node Removal
121
122
Remove nodes from the tree during transformation.
123
124
```python { .api }
125
class RemovalSentinel:
126
"""Sentinel type for indicating node removal."""
127
128
# Global instance for node removal
129
RemoveFromParent: RemovalSentinel
130
131
class FlattenSentinel:
132
"""Sentinel type for flattening sequences."""
133
134
class MaybeSentinel:
135
"""Sentinel type for optional values."""
136
```
137
138
## Usage Examples
139
140
### Basic Analysis Visitor
141
142
```python
143
import libcst as cst
144
145
class FunctionAnalyzer(cst.CSTVisitor):
146
def __init__(self):
147
self.function_names = []
148
self.call_counts = {}
149
150
def visit_FunctionDef(self, node):
151
self.function_names.append(node.name.value)
152
153
def visit_Call(self, node):
154
if isinstance(node.func, cst.Name):
155
name = node.func.value
156
self.call_counts[name] = self.call_counts.get(name, 0) + 1
157
158
# Use the visitor
159
source = '''
160
def foo():
161
bar()
162
baz()
163
bar()
164
165
def qux():
166
foo()
167
'''
168
169
module = cst.parse_module(source)
170
analyzer = FunctionAnalyzer()
171
module.visit(analyzer)
172
173
print(analyzer.function_names) # ['foo', 'qux']
174
print(analyzer.call_counts) # {'bar': 2, 'baz': 1, 'foo': 1}
175
```
176
177
### Code Transformation
178
179
```python
180
import libcst as cst
181
182
class PrintReplacer(cst.CSTTransformer):
183
"""Replace print() calls with logging.info()."""
184
185
def leave_Call(self, original_node, updated_node):
186
if isinstance(updated_node.func, cst.Name) and updated_node.func.value == "print":
187
# Replace print with logging.info
188
new_func = cst.Attribute(
189
value=cst.Name("logging"),
190
attr=cst.Name("info")
191
)
192
return updated_node.with_changes(func=new_func)
193
return updated_node
194
195
# Transform code
196
source = '''
197
def greet(name):
198
print("Hello, " + name)
199
print("Welcome!")
200
'''
201
202
module = cst.parse_module(source)
203
transformer = PrintReplacer()
204
new_module = module.visit(transformer)
205
206
print(new_module.code)
207
# Output shows print() replaced with logging.info()
208
```
209
210
### Metadata-Dependent Visitor
211
212
```python
213
import libcst as cst
214
from libcst.metadata import ScopeProvider, Scope
215
216
class VariableAnalyzer(cst.CSTVisitor):
217
METADATA_DEPENDENCIES = (ScopeProvider,)
218
219
def __init__(self):
220
self.variables = {}
221
222
def visit_Name(self, node):
223
scope = self.resolve(ScopeProvider)
224
if isinstance(scope, cst.metadata.FunctionScope):
225
scope_name = scope.name
226
if scope_name not in self.variables:
227
self.variables[scope_name] = []
228
self.variables[scope_name].append(node.value)
229
230
# Use with metadata wrapper
231
from libcst.metadata import MetadataWrapper
232
233
source = '''
234
def func1():
235
x = 1
236
y = 2
237
238
def func2():
239
a = 3
240
b = 4
241
'''
242
243
module = cst.parse_module(source)
244
wrapper = MetadataWrapper(module)
245
analyzer = VariableAnalyzer()
246
wrapper.visit(analyzer)
247
```
248
249
### Node Removal
250
251
```python
252
import libcst as cst
253
254
class CommentRemover(cst.CSTTransformer):
255
"""Remove all comments from code."""
256
257
def leave_Comment(self, original_node, updated_node):
258
return cst.RemoveFromParent
259
260
# Usage
261
source = '''
262
# This is a comment
263
def foo(): # Another comment
264
return 42
265
'''
266
267
module = cst.parse_module(source)
268
transformer = CommentRemover()
269
new_module = module.visit(transformer)
270
# Comments will be removed from the output
271
```
272
273
## Types
274
275
```python { .api }
276
# Base visitor types
277
CSTNodeT = TypeVar("CSTNodeT", bound=CSTNode)
278
CSTVisitorT = TypeVar("CSTVisitorT", bound=CSTVisitor)
279
280
# Visitor base classes
281
class CSTVisitor:
282
"""Base class for read-only CST traversal."""
283
284
class CSTTransformer(CSTVisitor):
285
"""Base class for CST transformation."""
286
287
class BatchableCSTVisitor(CSTVisitor):
288
"""Base class for batched visitors."""
289
290
class MetadataDependent:
291
"""Base class for metadata-dependent operations."""
292
METADATA_DEPENDENCIES: Sequence[Type[BaseMetadataProvider]]
293
294
# Sentinel types for special operations
295
class RemovalSentinel:
296
"""Indicates a node should be removed."""
297
298
class FlattenSentinel:
299
"""Indicates a sequence should be flattened."""
300
301
class MaybeSentinel:
302
"""Represents an optional value."""
303
304
# Global sentinel instances
305
RemoveFromParent: RemovalSentinel
306
DoNotCareSentinel: Any
307
308
# Metadata provider base type
309
class BaseMetadataProvider:
310
"""Base class for all metadata providers."""
311
312
# Exception types
313
class MetadataException(Exception):
314
"""Raised for metadata-related errors."""
315
316
class CSTLogicError(Exception):
317
"""Raised for internal visitor logic errors."""
318
```