0
# Pattern Matching
1
2
LibCST's matcher system provides declarative pattern matching for finding, extracting, and replacing specific code patterns in concrete syntax trees. The system supports complex matching logic, data extraction, and integration with the visitor framework.
3
4
## Capabilities
5
6
### Core Matching Functions
7
8
Find and analyze code patterns declaratively without writing custom visitors.
9
10
```python { .api }
11
def matches(node: CSTNode, matcher: Union[CSTNode, MatcherPattern]) -> bool:
12
"""
13
Check if a node matches a given pattern.
14
15
Parameters:
16
- node: CST node to test
17
- matcher: Pattern to match against
18
19
Returns:
20
bool: True if node matches the pattern
21
"""
22
23
def findall(tree: CSTNode, matcher: Union[CSTNode, MatcherPattern]) -> Sequence[CSTNode]:
24
"""
25
Find all nodes in tree that match the pattern.
26
27
Parameters:
28
- tree: CST tree to search
29
- matcher: Pattern to match
30
31
Returns:
32
Sequence[CSTNode]: All matching nodes
33
"""
34
35
def extract(node: CSTNode, matcher: Union[CSTNode, MatcherPattern]) -> Dict[str, Union[CSTNode, Sequence[CSTNode]]]:
36
"""
37
Extract captured groups from a matching node.
38
39
Parameters:
40
- node: CST node that matches the pattern
41
- matcher: Pattern with capture groups
42
43
Returns:
44
Dict[str, Union[CSTNode, Sequence[CSTNode]]]: Captured groups by name
45
"""
46
47
def extractall(tree: CSTNode, matcher: Union[CSTNode, MatcherPattern]) -> Sequence[Dict[str, Union[CSTNode, Sequence[CSTNode]]]]:
48
"""
49
Extract captured groups from all matching nodes in tree.
50
51
Parameters:
52
- tree: CST tree to search
53
- matcher: Pattern with capture groups
54
55
Returns:
56
Sequence[Dict]: List of captured groups from each match
57
"""
58
59
def replace(tree: CSTNode, matcher: Union[CSTNode, MatcherPattern], replacement: Union[CSTNode, Callable]) -> CSTNode:
60
"""
61
Replace all matching nodes with replacement.
62
63
Parameters:
64
- tree: CST tree to transform
65
- matcher: Pattern to match
66
- replacement: Replacement node or callable
67
68
Returns:
69
CSTNode: Transformed tree
70
"""
71
```
72
73
### Matcher Combinators
74
75
Compose complex matching logic from simple patterns.
76
77
```python { .api }
78
class AllOf:
79
"""Match if all sub-matchers match."""
80
def __init__(self, *matchers: Union[CSTNode, MatcherPattern]) -> None: ...
81
82
class OneOf:
83
"""Match if any sub-matcher matches."""
84
def __init__(self, *matchers: Union[CSTNode, MatcherPattern]) -> None: ...
85
86
class AtLeastN:
87
"""Match if at least N sub-matchers match."""
88
def __init__(self, n: int, matcher: Union[CSTNode, MatcherPattern]) -> None: ...
89
90
class AtMostN:
91
"""Match if at most N sub-matchers match."""
92
def __init__(self, n: int, matcher: Union[CSTNode, MatcherPattern]) -> None: ...
93
94
class ZeroOrMore:
95
"""Match zero or more occurrences."""
96
def __init__(self, matcher: Union[CSTNode, MatcherPattern]) -> None: ...
97
98
class ZeroOrOne:
99
"""Match zero or one occurrence."""
100
def __init__(self, matcher: Union[CSTNode, MatcherPattern]) -> None: ...
101
102
class DoesNotMatch:
103
"""Inverse matcher - match if sub-matcher does not match."""
104
def __init__(self, matcher: Union[CSTNode, MatcherPattern]) -> None: ...
105
106
class DoNotCare:
107
"""Wildcard matcher - matches any node."""
108
```
109
110
### Special Matchers
111
112
Advanced matching capabilities for specific use cases.
113
114
```python { .api }
115
class MatchIfTrue:
116
"""Match based on predicate function."""
117
def __init__(self, func: Callable[[CSTNode], bool]) -> None: ...
118
119
class MatchRegex:
120
"""Match string content with regular expressions."""
121
def __init__(self, pattern: str, flags: int = 0) -> None: ...
122
123
class MatchMetadata:
124
"""Match based on metadata values."""
125
def __init__(self, provider: Type[BaseMetadataProvider], metadata: Any) -> None: ...
126
127
class MatchMetadataIfTrue:
128
"""Match based on metadata predicate."""
129
def __init__(self, provider: Type[BaseMetadataProvider], func: Callable[[Any], bool]) -> None: ...
130
131
class SaveMatchedNode:
132
"""Capture matched nodes with a name."""
133
def __init__(self, name: str, matcher: Union[CSTNode, MatcherPattern] = DoNotCare()) -> None: ...
134
135
class TypeOf:
136
"""Match nodes of specific type."""
137
def __init__(self, node_type: Type[CSTNode]) -> None: ...
138
```
139
140
### Visitor Integration
141
142
Integrate matchers with the visitor framework for enhanced traversal control.
143
144
```python { .api }
145
def call_if_inside(matcher: Union[CSTNode, MatcherPattern]) -> Callable:
146
"""
147
Decorator to call visitor method only if inside matching context.
148
149
Parameters:
150
- matcher: Pattern that must match ancestor node
151
152
Returns:
153
Callable: Decorator function
154
"""
155
156
def call_if_not_inside(matcher: Union[CSTNode, MatcherPattern]) -> Callable:
157
"""
158
Decorator to call visitor method only if not inside matching context.
159
160
Parameters:
161
- matcher: Pattern that must not match ancestor node
162
163
Returns:
164
Callable: Decorator function
165
"""
166
167
def visit(*matchers: Union[CSTNode, MatcherPattern]) -> Callable:
168
"""
169
Decorator to call visitor method only for matching nodes.
170
171
Parameters:
172
- matchers: Patterns that must match the current node
173
174
Returns:
175
Callable: Decorator function
176
"""
177
178
def leave(*matchers: Union[CSTNode, MatcherPattern]) -> Callable:
179
"""
180
Decorator to call leave method only for matching nodes.
181
182
Parameters:
183
- matchers: Patterns that must match the current node
184
185
Returns:
186
Callable: Decorator function
187
"""
188
```
189
190
### Matcher-Enabled Visitors
191
192
Base classes that provide matcher decorator support.
193
194
```python { .api }
195
class MatcherDecoratableVisitor(CSTVisitor):
196
"""Base visitor with matcher decorator support."""
197
198
class MatcherDecoratableTransformer(CSTTransformer):
199
"""Base transformer with matcher decorator support."""
200
201
class MatchDecoratorMismatch(Exception):
202
"""Raised when matcher decorators are used incorrectly."""
203
```
204
205
## Usage Examples
206
207
### Basic Pattern Matching
208
209
```python
210
import libcst as cst
211
from libcst import matchers as m
212
213
source = '''
214
def foo():
215
x = 42
216
y = "hello"
217
z = [1, 2, 3]
218
'''
219
220
module = cst.parse_module(source)
221
222
# Find all assignment statements
223
assignments = m.findall(module, m.Assign())
224
print(f"Found {len(assignments)} assignments")
225
226
# Check if specific pattern exists
227
has_string_assignment = m.matches(
228
module,
229
m.Module(body=[
230
m.AtLeastN(1, m.SimpleStatementLine(body=[
231
m.Assign(value=m.SimpleString())
232
]))
233
])
234
)
235
print(f"Has string assignment: {has_string_assignment}")
236
```
237
238
### Data Extraction
239
240
```python
241
import libcst as cst
242
from libcst import matchers as m
243
244
source = '''
245
def calculate(x, y):
246
return x + y
247
248
def process(data):
249
return data * 2
250
'''
251
252
module = cst.parse_module(source)
253
254
# Extract function names and parameter counts
255
function_pattern = m.FunctionDef(
256
name=m.SaveMatchedNode("name"),
257
params=m.SaveMatchedNode("params")
258
)
259
260
matches = m.extractall(module, function_pattern)
261
for match in matches:
262
name = match["name"].value
263
param_count = len(match["params"].params)
264
print(f"Function {name} has {param_count} parameters")
265
```
266
267
### Pattern Replacement
268
269
```python
270
import libcst as cst
271
from libcst import matchers as m
272
273
source = '''
274
def foo():
275
print("debug: starting")
276
result = calculate()
277
print("debug: finished")
278
return result
279
'''
280
281
module = cst.parse_module(source)
282
283
# Replace debug print statements with logging calls
284
debug_print = m.Call(
285
func=m.Name("print"),
286
args=[m.Arg(value=m.SimpleString(value=m.MatchRegex(r'"debug:.*"')))]
287
)
288
289
def make_logging_call(node):
290
# Extract the debug message
291
message = node.args[0].value.value
292
return cst.Call(
293
func=cst.Attribute(value=cst.Name("logging"), attr=cst.Name("debug")),
294
args=[cst.Arg(value=cst.SimpleString(message))]
295
)
296
297
new_module = m.replace(module, debug_print, make_logging_call)
298
print(new_module.code)
299
```
300
301
### Visitor with Matcher Decorators
302
303
```python
304
import libcst as cst
305
from libcst import matchers as m
306
307
class SecurityAnalyzer(m.MatcherDecoratableVisitor):
308
def __init__(self):
309
self.security_issues = []
310
311
@m.visit(m.Call(func=m.Name("eval")))
312
def visit_eval_call(self, node):
313
self.security_issues.append("Dangerous eval() call found")
314
315
@m.visit(m.Call(func=m.Name("exec")))
316
def visit_exec_call(self, node):
317
self.security_issues.append("Dangerous exec() call found")
318
319
@m.call_if_inside(m.FunctionDef(name=m.Name("__init__")))
320
@m.visit(m.Assign())
321
def visit_init_assignment(self, node):
322
# Only called for assignments inside __init__ methods
323
pass
324
325
# Usage
326
source = '''
327
class MyClass:
328
def __init__(self):
329
self.x = eval("42")
330
331
def process(self):
332
exec("print('hello')")
333
'''
334
335
module = cst.parse_module(source)
336
analyzer = SecurityAnalyzer()
337
module.visit(analyzer)
338
print(analyzer.security_issues)
339
```
340
341
### Complex Pattern Matching
342
343
```python
344
import libcst as cst
345
from libcst import matchers as m
346
347
# Find all function calls inside try blocks
348
complex_pattern = m.Try(
349
body=m.SimpleStatementSuite(body=[
350
m.ZeroOrMore(m.SimpleStatementLine(body=[
351
m.OneOf(
352
m.Expr(value=m.Call()), # Expression statements with calls
353
m.Assign(value=m.Call()) # Assignments with call values
354
)
355
]))
356
])
357
)
358
359
# Find functions that take at least 3 parameters
360
many_param_functions = m.FunctionDef(
361
params=m.Parameters(params=m.AtLeastN(3, m.Param()))
362
)
363
364
# Match string literals containing SQL keywords
365
sql_strings = m.SimpleString(
366
value=m.MatchRegex(r'".*\b(SELECT|INSERT|UPDATE|DELETE)\b.*"', re.IGNORECASE)
367
)
368
```
369
370
## Types
371
372
```python { .api }
373
# Core matcher types
374
MatcherPattern = Union[
375
CSTNode,
376
"AllOf",
377
"OneOf",
378
"AtLeastN",
379
"AtMostN",
380
"ZeroOrMore",
381
"ZeroOrOne",
382
"DoesNotMatch",
383
"DoNotCare",
384
"MatchIfTrue",
385
"MatchRegex",
386
"MatchMetadata",
387
"MatchMetadataIfTrue",
388
"SaveMatchedNode",
389
"TypeOf"
390
]
391
392
# Visitor decorator types
393
VisitorMethod = Callable[[CSTVisitor, CSTNode], None]
394
TransformerMethod = Callable[[CSTTransformer, CSTNode, CSTNode], Union[CSTNode, RemovalSentinel]]
395
396
# Exception types
397
class MatchDecoratorMismatch(Exception):
398
"""Raised when matcher decorators are misused."""
399
400
# Sentinel for wildcards
401
class DoNotCareSentinel:
402
"""Wildcard matcher that matches anything."""
403
404
DoNotCare: DoNotCareSentinel
405
```