0
# Metadata Analysis
1
2
LibCST's metadata system attaches semantic information to CST nodes, enabling advanced static analysis capabilities including scope analysis, position tracking, qualified name resolution, and type inference. The system uses a provider architecture for extensible metadata computation.
3
4
## Capabilities
5
6
### Metadata Wrapper
7
8
Central coordinator for metadata computation and resolution.
9
10
```python { .api }
11
class MetadataWrapper:
12
"""Main interface for metadata-enabled CST analysis."""
13
14
def __init__(self, module: Module, cache: Optional[Dict] = None) -> None:
15
"""
16
Initialize metadata wrapper for a module.
17
18
Parameters:
19
- module: CST module to analyze
20
- cache: Optional cache for metadata providers
21
"""
22
23
def resolve(self, provider: Type[BaseMetadataProvider]) -> Mapping[CSTNode, Any]:
24
"""
25
Resolve metadata for all nodes using the specified provider.
26
27
Parameters:
28
- provider: Metadata provider class
29
30
Returns:
31
Mapping[CSTNode, Any]: Metadata values by node
32
"""
33
34
def resolve_many(self, providers: Sequence[Type[BaseMetadataProvider]]) -> Dict[Type[BaseMetadataProvider], Mapping[CSTNode, Any]]:
35
"""
36
Resolve multiple metadata providers efficiently.
37
38
Parameters:
39
- providers: Sequence of provider classes
40
41
Returns:
42
Dict[Type[BaseMetadataProvider], Mapping[CSTNode, Any]]: Results by provider
43
"""
44
45
def visit(self, visitor: MetadataDependent) -> CSTNode:
46
"""
47
Visit module with metadata-dependent visitor.
48
49
Parameters:
50
- visitor: Visitor that requires metadata
51
52
Returns:
53
CSTNode: Potentially transformed module
54
"""
55
```
56
57
### Position Tracking
58
59
Track source code positions for CST nodes.
60
61
```python { .api }
62
class PositionProvider(BaseMetadataProvider):
63
"""Provides line/column positions for CST nodes."""
64
65
class WhitespaceInclusivePositionProvider(BaseMetadataProvider):
66
"""Provides positions including leading/trailing whitespace."""
67
68
class ByteSpanPositionProvider(BaseMetadataProvider):
69
"""Provides byte-based position spans."""
70
71
class CodePosition:
72
"""Represents a line/column position in source code."""
73
line: int
74
column: int
75
76
class CodeRange:
77
"""Represents a start/end position range."""
78
start: CodePosition
79
end: CodePosition
80
81
class CodeSpan:
82
"""Represents a byte-based span."""
83
start: int
84
length: int
85
```
86
87
### Scope Analysis
88
89
Analyze variable scopes, assignments, and accesses.
90
91
```python { .api }
92
class ScopeProvider(BaseMetadataProvider):
93
"""Analyzes variable scopes and assignments."""
94
95
class Scope:
96
"""Base class for all scope types."""
97
parent: Optional["Scope"]
98
99
def __contains__(self, name: str) -> bool:
100
"""Check if name is defined in this scope."""
101
102
def __getitem__(self, name: str) -> Collection["BaseAssignment"]:
103
"""Get assignments for a name in this scope."""
104
105
class GlobalScope(Scope):
106
"""Module-level scope."""
107
108
class FunctionScope(Scope):
109
"""Function-level scope."""
110
name: str
111
112
class ClassScope(Scope):
113
"""Class-level scope."""
114
name: str
115
116
class ComprehensionScope(Scope):
117
"""Comprehension-level scope."""
118
119
class BuiltinScope(Scope):
120
"""Built-in names scope."""
121
122
class Assignment:
123
"""Tracks variable assignments."""
124
name: str
125
node: CSTNode
126
scope: Scope
127
128
class BuiltinAssignment(Assignment):
129
"""Built-in variable assignment."""
130
131
class ImportAssignment(Assignment):
132
"""Import-based assignment."""
133
134
class Access:
135
"""Tracks variable access."""
136
name: str
137
node: CSTNode
138
scope: Scope
139
140
# Type aliases
141
Accesses = Collection[Access]
142
Assignments = Collection[BaseAssignment]
143
```
144
145
### Qualified Names
146
147
Resolve qualified names for imported and defined symbols.
148
149
```python { .api }
150
class QualifiedNameProvider(BaseMetadataProvider):
151
"""Provides qualified names for nodes."""
152
153
class FullyQualifiedNameProvider(BaseMetadataProvider):
154
"""Provides fully qualified names including module paths."""
155
156
class QualifiedName:
157
"""Represents a qualified name."""
158
name: str
159
source: QualifiedNameSource
160
161
class QualifiedNameSource:
162
"""Source information for qualified names."""
163
IMPORT: ClassVar[int]
164
BUILTIN: ClassVar[int]
165
LOCAL: ClassVar[int]
166
```
167
168
### Expression Context
169
170
Determine Load/Store/Del context for expressions.
171
172
```python { .api }
173
class ExpressionContextProvider(BaseMetadataProvider):
174
"""Determines expression context (Load/Store/Del)."""
175
176
class ExpressionContext:
177
"""Expression context enumeration."""
178
LOAD: ClassVar[int]
179
STORE: ClassVar[int]
180
DEL: ClassVar[int]
181
```
182
183
### Parent Node Relationships
184
185
Track parent-child relationships in the CST.
186
187
```python { .api }
188
class ParentNodeProvider(BaseMetadataProvider):
189
"""Provides parent node relationships."""
190
```
191
192
### Advanced Providers
193
194
Additional metadata providers for specialized analysis.
195
196
```python { .api }
197
class TypeInferenceProvider(BaseMetadataProvider):
198
"""Experimental type inference provider."""
199
200
class FullRepoManager:
201
"""Manager for cross-file analysis."""
202
203
def __init__(self, repo_root: str, paths: Sequence[str], providers: Sequence[Type[BaseMetadataProvider]]) -> None:
204
"""
205
Initialize repository-wide analysis.
206
207
Parameters:
208
- repo_root: Root directory of repository
209
- paths: Python files to analyze
210
- providers: Metadata providers to use
211
"""
212
213
class AccessorProvider(BaseMetadataProvider):
214
"""Provides accessor metadata."""
215
216
class FilePathProvider(BaseMetadataProvider):
217
"""Provides file path information."""
218
```
219
220
### Provider Base Classes
221
222
Foundation for implementing custom metadata providers.
223
224
```python { .api }
225
class BaseMetadataProvider:
226
"""Base class for all metadata providers."""
227
228
def visit_Module(self, node: Module) -> None:
229
"""Visit module node."""
230
231
class BatchableMetadataProvider(BaseMetadataProvider):
232
"""Base class for batchable providers."""
233
234
class VisitorMetadataProvider(BaseMetadataProvider):
235
"""Base class for visitor-based providers."""
236
237
ProviderT = TypeVar("ProviderT", bound=BaseMetadataProvider)
238
```
239
240
## Usage Examples
241
242
### Basic Position Tracking
243
244
```python
245
import libcst as cst
246
from libcst.metadata import MetadataWrapper, PositionProvider
247
248
source = '''
249
def foo():
250
x = 42
251
return x
252
'''
253
254
module = cst.parse_module(source)
255
wrapper = MetadataWrapper(module)
256
positions = wrapper.resolve(PositionProvider)
257
258
# Find positions of all Name nodes
259
for node in cst.metadata.findall(module, cst.Name):
260
if node in positions:
261
pos = positions[node]
262
print(f"Name '{node.value}' at line {pos.start.line}, column {pos.start.column}")
263
```
264
265
### Scope Analysis
266
267
```python
268
import libcst as cst
269
from libcst.metadata import MetadataWrapper, ScopeProvider
270
271
source = '''
272
x = "global"
273
274
def outer():
275
y = "outer"
276
277
def inner():
278
z = "inner"
279
print(x, y, z) # Access variables from different scopes
280
281
inner()
282
283
outer()
284
'''
285
286
module = cst.parse_module(source)
287
wrapper = MetadataWrapper(module)
288
scopes = wrapper.resolve(ScopeProvider)
289
290
# Analyze variable assignments and accesses
291
for node, scope in scopes.items():
292
if isinstance(node, cst.Name):
293
scope_type = type(scope).__name__
294
print(f"Name '{node.value}' in {scope_type}")
295
296
# Check if this is an assignment or access
297
if node.value in scope:
298
assignments = scope[node.value]
299
print(f" Has {len(assignments)} assignments in this scope")
300
```
301
302
### Qualified Name Resolution
303
304
```python
305
import libcst as cst
306
from libcst.metadata import MetadataWrapper, QualifiedNameProvider
307
308
source = '''
309
import os.path
310
from collections import Counter
311
from .local import helper
312
313
def process():
314
return os.path.join("a", "b")
315
316
def analyze(data):
317
return Counter(data)
318
319
def work():
320
return helper.process()
321
'''
322
323
module = cst.parse_module(source)
324
wrapper = MetadataWrapper(module)
325
qualified_names = wrapper.resolve(QualifiedNameProvider)
326
327
# Find qualified names for all function calls
328
for node in cst.matchers.findall(module, cst.Call()):
329
if node.func in qualified_names:
330
qnames = qualified_names[node.func]
331
for qname in qnames:
332
print(f"Call to: {qname.name}")
333
```
334
335
### Metadata-Dependent Visitor
336
337
```python
338
import libcst as cst
339
from libcst.metadata import MetadataWrapper, ScopeProvider, PositionProvider
340
341
class VariableTracker(cst.MetadataDependent):
342
METADATA_DEPENDENCIES = (ScopeProvider, PositionProvider)
343
344
def __init__(self):
345
self.assignments = []
346
self.accesses = []
347
348
def visit_Name(self, node):
349
scope = self.resolve(ScopeProvider)[node]
350
position = self.resolve(PositionProvider)[node]
351
352
# Determine if this is assignment or access based on context
353
# (simplified - real implementation would check expression context)
354
if isinstance(node.parent, cst.Assign) and node in node.parent.targets:
355
self.assignments.append({
356
'name': node.value,
357
'line': position.start.line,
358
'scope': type(scope).__name__
359
})
360
else:
361
self.accesses.append({
362
'name': node.value,
363
'line': position.start.line,
364
'scope': type(scope).__name__
365
})
366
367
# Usage
368
source = '''
369
def example():
370
x = 1 # Assignment
371
y = x + 2 # Access to x, assignment to y
372
return y # Access to y
373
'''
374
375
module = cst.parse_module(source)
376
wrapper = MetadataWrapper(module)
377
tracker = VariableTracker()
378
wrapper.visit(tracker)
379
380
print("Assignments:", tracker.assignments)
381
print("Accesses:", tracker.accesses)
382
```
383
384
### Cross-File Analysis
385
386
```python
387
from libcst.metadata import FullRepoManager, ScopeProvider, QualifiedNameProvider
388
389
# Analyze multiple files in a repository
390
manager = FullRepoManager(
391
repo_root="/path/to/repo",
392
paths=["module1.py", "module2.py", "package/__init__.py"],
393
providers=[ScopeProvider, QualifiedNameProvider]
394
)
395
396
# Get metadata for all files
397
repo_metadata = manager.get_cache()
398
for file_path, file_metadata in repo_metadata.items():
399
print(f"Analyzing {file_path}")
400
for provider, node_metadata in file_metadata.items():
401
print(f" {provider.__name__}: {len(node_metadata)} nodes")
402
```
403
404
## Types
405
406
```python { .api }
407
# Position types
408
class CodePosition:
409
line: int
410
column: int
411
412
class CodeRange:
413
start: CodePosition
414
end: CodePosition
415
416
class CodeSpan:
417
start: int
418
length: int
419
420
# Scope types
421
class Scope:
422
parent: Optional["Scope"]
423
424
class GlobalScope(Scope): ...
425
class FunctionScope(Scope):
426
name: str
427
class ClassScope(Scope):
428
name: str
429
class ComprehensionScope(Scope): ...
430
class BuiltinScope(Scope): ...
431
432
# Assignment and access types
433
class BaseAssignment:
434
name: str
435
node: CSTNode
436
scope: Scope
437
438
class Assignment(BaseAssignment): ...
439
class BuiltinAssignment(BaseAssignment): ...
440
class ImportAssignment(BaseAssignment): ...
441
442
class Access:
443
name: str
444
node: CSTNode
445
scope: Scope
446
447
Accesses = Collection[Access]
448
Assignments = Collection[BaseAssignment]
449
450
# Qualified name types
451
class QualifiedName:
452
name: str
453
source: QualifiedNameSource
454
455
class QualifiedNameSource:
456
IMPORT: ClassVar[int]
457
BUILTIN: ClassVar[int]
458
LOCAL: ClassVar[int]
459
460
# Expression context
461
class ExpressionContext:
462
LOAD: ClassVar[int]
463
STORE: ClassVar[int]
464
DEL: ClassVar[int]
465
466
# Provider types
467
ProviderT = TypeVar("ProviderT", bound=BaseMetadataProvider)
468
469
class BaseMetadataProvider:
470
"""Base class for metadata providers."""
471
472
class BatchableMetadataProvider(BaseMetadataProvider): ...
473
class VisitorMetadataProvider(BaseMetadataProvider): ...
474
475
# Exception types
476
class MetadataException(Exception):
477
"""Raised for metadata-related errors."""
478
```