0
# LibCST Utilities
1
2
LibCST provides a comprehensive suite of utility functions to help developers work with Concrete Syntax Trees efficiently. These utilities cover everything from module resolution and template parsing to node introspection and testing helpers.
3
4
## Helper Functions
5
6
LibCST's helper functions provide essential utilities for working with CST nodes, module imports, templates, and more.
7
8
### Module and Package Utilities
9
10
These functions help with module resolution, import handling, and calculating module paths:
11
12
```python { .api }
13
from libcst.helpers import (
14
calculate_module_and_package,
15
get_absolute_module,
16
get_absolute_module_for_import,
17
get_absolute_module_for_import_or_raise,
18
get_absolute_module_from_package,
19
get_absolute_module_from_package_for_import,
20
get_absolute_module_from_package_for_import_or_raise,
21
insert_header_comments,
22
ModuleNameAndPackage,
23
)
24
```
25
26
**calculate_module_and_package**
27
```python
28
def calculate_module_and_package(
29
repo_root: StrPath,
30
filename: StrPath,
31
use_pyproject_toml: bool = False
32
) -> ModuleNameAndPackage
33
```
34
35
Calculates the Python module name and package for a given file path relative to a repository root.
36
37
```python
38
from libcst.helpers import calculate_module_and_package
39
from pathlib import Path
40
41
# Calculate module info for a file
42
result = calculate_module_and_package(
43
repo_root="/project/root",
44
filename="/project/root/src/mypackage/utils.py"
45
)
46
# result.name = "src.mypackage.utils"
47
# result.package = "src.mypackage"
48
```
49
50
**get_absolute_module**
51
```python
52
def get_absolute_module(
53
current_module: Optional[str],
54
module_name: Optional[str],
55
num_dots: int
56
) -> Optional[str]
57
```
58
59
Resolves relative imports to absolute module names based on the current module and number of dots.
60
61
```python
62
from libcst.helpers import get_absolute_module
63
64
# Resolve relative import
65
absolute = get_absolute_module(
66
current_module="mypackage.submodule.file",
67
module_name="utils",
68
num_dots=2 # from ..utils import something
69
)
70
# Returns: "mypackage.utils"
71
```
72
73
**get_absolute_module_for_import**
74
```python
75
def get_absolute_module_for_import(
76
current_module: Optional[str],
77
import_node: ImportFrom
78
) -> Optional[str]
79
```
80
81
Extracts and resolves the absolute module name from an ImportFrom CST node.
82
83
**insert_header_comments**
84
```python
85
def insert_header_comments(node: Module, comments: List[str]) -> Module
86
```
87
88
Inserts comments after the last non-empty line in a module header, useful for adding copyright notices or other header comments.
89
90
```python
91
import libcst as cst
92
from libcst.helpers import insert_header_comments
93
94
# Parse a module
95
module = cst.parse_module("print('hello')")
96
97
# Add header comments
98
new_module = insert_header_comments(module, [
99
"# Copyright 2024 MyCompany",
100
"# Licensed under MIT"
101
])
102
```
103
104
### Template Parsing Functions
105
106
Template functions allow you to parse Python code templates with variable substitution:
107
108
```python { .api }
109
from libcst.helpers import (
110
parse_template_expression,
111
parse_template_module,
112
parse_template_statement,
113
)
114
```
115
116
**parse_template_module**
117
```python
118
def parse_template_module(
119
template: str,
120
config: PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
121
**template_replacements: ValidReplacementType,
122
) -> Module
123
```
124
125
Parses an entire Python module template with variable substitution.
126
127
```python
128
import libcst as cst
129
from libcst.helpers import parse_template_module
130
131
# Parse a module template with substitutions
132
module = parse_template_module(
133
"from {mod} import {name}\n\ndef {func}():\n return {value}",
134
mod=cst.Name("math"),
135
name=cst.Name("pi"),
136
func=cst.Name("get_pi"),
137
value=cst.Name("pi")
138
)
139
```
140
141
**parse_template_statement**
142
```python
143
def parse_template_statement(
144
template: str,
145
config: PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
146
**template_replacements: ValidReplacementType,
147
) -> Union[SimpleStatementLine, BaseCompoundStatement]
148
```
149
150
Parses a statement template with variable substitution.
151
152
```python
153
from libcst.helpers import parse_template_statement
154
155
# Parse a statement template
156
stmt = parse_template_statement(
157
"assert {condition}, {message}",
158
condition=cst.parse_expression("x > 0"),
159
message=cst.SimpleString('"Value must be positive"')
160
)
161
```
162
163
**parse_template_expression**
164
```python
165
def parse_template_expression(
166
template: str,
167
config: PartialParserConfig = _DEFAULT_PARTIAL_PARSER_CONFIG,
168
**template_replacements: ValidReplacementType,
169
) -> BaseExpression
170
```
171
172
Parses an expression template with variable substitution.
173
174
```python
175
from libcst.helpers import parse_template_expression
176
177
# Parse an expression template
178
expr = parse_template_expression(
179
"{left} + {right}",
180
left=cst.Name("x"),
181
right=cst.Integer("42")
182
)
183
```
184
185
### Node Introspection Utilities
186
187
These functions help examine and analyze CST node structure:
188
189
```python { .api }
190
from libcst.helpers import (
191
get_node_fields,
192
get_field_default_value,
193
is_whitespace_node_field,
194
is_syntax_node_field,
195
is_default_node_field,
196
filter_node_fields,
197
)
198
```
199
200
**get_node_fields**
201
```python
202
def get_node_fields(node: CSTNode) -> Sequence[dataclasses.Field[CSTNode]]
203
```
204
205
Returns all dataclass fields for a CST node.
206
207
```python
208
import libcst as cst
209
from libcst.helpers import get_node_fields
210
211
# Get all fields of a Name node
212
name_node = cst.Name("variable")
213
fields = get_node_fields(name_node)
214
for field in fields:
215
print(f"Field: {field.name}, Type: {field.type}")
216
```
217
218
**filter_node_fields**
219
```python
220
def filter_node_fields(
221
node: CSTNode,
222
*,
223
show_defaults: bool,
224
show_syntax: bool,
225
show_whitespace: bool,
226
) -> Sequence[dataclasses.Field[CSTNode]]
227
```
228
229
Returns a filtered list of node fields based on various criteria.
230
231
```python
232
from libcst.helpers import filter_node_fields
233
234
# Get only non-default, non-whitespace fields
235
filtered_fields = filter_node_fields(
236
node,
237
show_defaults=False,
238
show_syntax=True,
239
show_whitespace=False
240
)
241
```
242
243
### Name Resolution Helpers
244
245
Functions for extracting qualified names from CST nodes:
246
247
```python { .api }
248
from libcst.helpers import (
249
get_full_name_for_node,
250
get_full_name_for_node_or_raise,
251
)
252
```
253
254
**get_full_name_for_node**
255
```python
256
def get_full_name_for_node(node: Union[str, CSTNode]) -> Optional[str]
257
```
258
259
Extracts the full qualified name from various CST node types (Name, Attribute, Call, etc.).
260
261
```python
262
import libcst as cst
263
from libcst.helpers import get_full_name_for_node
264
265
# Extract name from various node types
266
attr_node = cst.parse_expression("os.path.join")
267
name = get_full_name_for_node(attr_node) # Returns: "os.path.join"
268
269
call_node = cst.parse_expression("math.sqrt(4)")
270
name = get_full_name_for_node(call_node) # Returns: "math.sqrt"
271
```
272
273
### Type Checking Utilities
274
275
```python { .api }
276
from libcst.helpers import ensure_type
277
```
278
279
**ensure_type**
280
```python
281
def ensure_type(node: object, nodetype: Type[T]) -> T
282
```
283
284
Type-safe casting with runtime validation for CST nodes.
285
286
```python
287
from libcst.helpers import ensure_type
288
import libcst as cst
289
290
# Safely cast to a specific node type
291
expr = cst.parse_expression("variable_name")
292
name_node = ensure_type(expr, cst.Name) # Validates it's actually a Name node
293
```
294
295
### Path Utilities
296
297
```python { .api }
298
from libcst.helpers.paths import chdir
299
```
300
301
**chdir**
302
```python
303
@contextmanager
304
def chdir(path: StrPath) -> Generator[Path, None, None]
305
```
306
307
Context manager for temporarily changing the working directory.
308
309
```python
310
from libcst.helpers.paths import chdir
311
from pathlib import Path
312
313
# Temporarily change to another directory
314
with chdir("/some/other/path") as new_path:
315
# Work in the new directory
316
current = Path.cwd() # Points to /some/other/path
317
# Automatically returns to original directory
318
```
319
320
### Matcher Conversion Utilities
321
322
```python { .api }
323
from libcst.helpers.matchers import node_to_matcher
324
```
325
326
**node_to_matcher**
327
```python
328
def node_to_matcher(
329
node: CSTNode,
330
*,
331
match_syntactic_trivia: bool = False
332
) -> matchers.BaseMatcherNode
333
```
334
335
Converts a concrete CST node into a matcher for pattern matching.
336
337
```python
338
import libcst as cst
339
from libcst.helpers.matchers import node_to_matcher
340
341
# Convert node to matcher
342
node = cst.parse_expression("variable_name")
343
matcher = node_to_matcher(node)
344
345
# Use matcher to find similar patterns
346
# (useful in codemods and analysis tools)
347
```
348
349
## Display and Debugging Utilities
350
351
LibCST provides utilities for visualizing and debugging CST structures.
352
353
### Text Display
354
355
```python { .api }
356
from libcst.display import dump
357
```
358
359
**dump**
360
```python
361
def dump(
362
node: CSTNode,
363
*,
364
indent: str = " ",
365
show_defaults: bool = False,
366
show_syntax: bool = False,
367
show_whitespace: bool = False,
368
) -> str
369
```
370
371
Returns a string representation of a CST node with configurable detail levels.
372
373
```python
374
import libcst as cst
375
from libcst.display import dump
376
377
# Parse some code
378
node = cst.parse_expression("x + y * 2")
379
380
# Basic representation (minimal)
381
print(dump(node))
382
383
# Detailed representation showing all fields
384
print(dump(node, show_defaults=True, show_syntax=True, show_whitespace=True))
385
386
# Output example:
387
# BinaryOperation(
388
# left=Name("x"),
389
# operator=Add(),
390
# right=BinaryOperation(
391
# left=Name("y"),
392
# operator=Multiply(),
393
# right=Integer("2")
394
# )
395
# )
396
```
397
398
### GraphViz Visualization
399
400
```python { .api }
401
from libcst.display import dump_graphviz
402
```
403
404
**dump_graphviz**
405
```python
406
def dump_graphviz(
407
node: object,
408
*,
409
show_defaults: bool = False,
410
show_syntax: bool = False,
411
show_whitespace: bool = False,
412
) -> str
413
```
414
415
Generates a GraphViz .dot representation for visualizing CST structure as a graph.
416
417
```python
418
import libcst as cst
419
from libcst.display import dump_graphviz
420
421
# Parse some code
422
node = cst.parse_expression("len(items)")
423
424
# Generate GraphViz representation
425
dot_content = dump_graphviz(node)
426
427
# Save to file and render with GraphViz tools
428
with open("ast_graph.dot", "w") as f:
429
f.write(dot_content)
430
431
# Then use: dot -Tpng ast_graph.dot -o ast_graph.png
432
```
433
434
The generated GraphViz output can be rendered to various formats (PNG, SVG, PDF) using GraphViz tools, providing visual tree representations of CST structures that are invaluable for understanding complex code structures.
435
436
## Testing Utilities
437
438
LibCST provides comprehensive testing utilities for unit tests and codemod development.
439
440
### Base Testing Framework
441
442
```python { .api }
443
from libcst.testing import UnitTest
444
from libcst.testing.utils import data_provider, none_throws
445
```
446
447
**UnitTest**
448
```python
449
class UnitTest(TestCase, metaclass=BaseTestMeta)
450
```
451
452
Enhanced unittest.TestCase with data provider support and other testing conveniences.
453
454
```python
455
from libcst.testing import UnitTest, data_provider
456
457
class MyTests(UnitTest):
458
@data_provider([
459
("input1", "expected1"),
460
("input2", "expected2"),
461
])
462
def test_with_data(self, input_val: str, expected: str) -> None:
463
# Test implementation
464
pass
465
```
466
467
**data_provider**
468
```python
469
def data_provider(
470
static_data: StaticDataType,
471
*,
472
test_limit: int = DEFAULT_TEST_LIMIT
473
) -> Callable[[Callable], Callable]
474
```
475
476
Decorator that generates multiple test methods from a data set.
477
478
```python
479
from libcst.testing.utils import data_provider
480
481
@data_provider([
482
{"name": "Alice", "age": 30},
483
{"name": "Bob", "age": 25},
484
])
485
def test_person_data(self, name: str, age: int) -> None:
486
# This creates test_person_data_0 and test_person_data_1
487
pass
488
```
489
490
**none_throws**
491
```python
492
def none_throws(value: Optional[T], message: str = "Unexpected None value") -> T
493
```
494
495
Assertion helper for non-None values with better error messages.
496
497
```python
498
from libcst.testing.utils import none_throws
499
500
def test_something(self) -> None:
501
result = get_optional_value()
502
# Safely unwrap Optional value with clear error message
503
actual_value = none_throws(result, "Expected non-None result from get_optional_value")
504
```
505
506
### Codemod Testing
507
508
```python { .api }
509
from libcst.codemod import CodemodTest
510
```
511
512
**CodemodTest**
513
```python
514
class CodemodTest(_CodemodTest, UnitTest):
515
TRANSFORM: Type[Codemod] = ... # Set to your codemod class
516
```
517
518
Base class for testing codemods with convenient assertion methods.
519
520
```python
521
from libcst.codemod import CodemodTest
522
from my_codemod import MyCodemod
523
524
class TestMyCodemod(CodemodTest):
525
TRANSFORM = MyCodemod
526
527
def test_simple_transformation(self) -> None:
528
# Test a codemod transformation
529
self.assertCodemod(
530
before="""
531
def old_function():
532
pass
533
""",
534
after="""
535
def new_function():
536
pass
537
""",
538
# Any arguments to pass to the codemod constructor
539
)
540
```
541
542
**assertCodemod**
543
```python
544
def assertCodemod(
545
self,
546
before: str,
547
after: str,
548
*args: object,
549
context_override: Optional[CodemodContext] = None,
550
python_version: Optional[str] = None,
551
expected_warnings: Optional[Sequence[str]] = None,
552
expected_skip: bool = False,
553
**kwargs: object,
554
) -> None
555
```
556
557
Comprehensive codemod testing with support for warnings, skip conditions, and custom contexts.
558
559
```python
560
def test_codemod_with_warnings(self) -> None:
561
self.assertCodemod(
562
before="old_code",
563
after="new_code",
564
expected_warnings=["Deprecated function usage detected"],
565
python_version="3.8"
566
)
567
568
def test_codemod_skip(self) -> None:
569
self.assertCodemod(
570
before="code_that_should_be_skipped",
571
after="code_that_should_be_skipped", # Same as before
572
expected_skip=True
573
)
574
```
575
576
**assertCodeEqual**
577
```python
578
def assertCodeEqual(self, expected: str, actual: str) -> None
579
```
580
581
Code-aware string comparison that normalizes whitespace and handles multi-line strings.
582
583
**make_fixture_data**
584
```python
585
@staticmethod
586
def make_fixture_data(data: str) -> str
587
```
588
589
Normalizes test fixture code by removing leading/trailing whitespace and ensuring proper newlines.
590
591
```python
592
def test_fixture_normalization(self) -> None:
593
# These strings are automatically normalized
594
code = self.make_fixture_data("""
595
def example():
596
return True
597
""")
598
# Becomes: "def example():\n return True\n"
599
```
600
601
## Usage Examples
602
603
### Complete Workflow Example
604
605
Here's a comprehensive example showing how to use various LibCST utilities together:
606
607
```python
608
import libcst as cst
609
from libcst.helpers import (
610
parse_template_module,
611
get_full_name_for_node,
612
calculate_module_and_package,
613
insert_header_comments
614
)
615
from libcst.display import dump
616
from libcst.testing import UnitTest, data_provider
617
618
class CodeAnalysisExample(UnitTest):
619
620
@data_provider([
621
("simple_function", "def foo(): pass"),
622
("class_definition", "class Bar: pass"),
623
])
624
def test_code_analysis(self, name: str, code: str) -> None:
625
# Parse the code
626
module = cst.parse_module(code)
627
628
# Analyze and display structure
629
print(f"Analysis of {name}:")
630
print(dump(module, show_syntax=True))
631
632
# Extract function/class names
633
class NameCollector(cst.CSTVisitor):
634
def __init__(self) -> None:
635
self.names: List[str] = []
636
637
def visit_FunctionDef(self, node: cst.FunctionDef) -> None:
638
name = get_full_name_for_node(node.name)
639
if name:
640
self.names.append(f"function: {name}")
641
642
def visit_ClassDef(self, node: cst.ClassDef) -> None:
643
name = get_full_name_for_node(node.name)
644
if name:
645
self.names.append(f"class: {name}")
646
647
collector = NameCollector()
648
module.visit(collector)
649
650
# Verify we found expected definitions
651
self.assertGreater(len(collector.names), 0)
652
653
def generate_boilerplate_code():
654
"""Example of generating code with templates"""
655
656
# Create a module template
657
template = """
658
{header_comment}
659
660
from typing import {type_imports}
661
662
class {class_name}:
663
def __init__(self, {init_params}) -> None:
664
{init_body}
665
666
def {method_name}(self) -> {return_type}:
667
{method_body}
668
"""
669
670
# Generate code using template
671
module = parse_template_module(
672
template,
673
header_comment=cst.SimpleStatementLine([
674
cst.Expr(cst.SimpleString('"""Generated class module."""'))
675
]),
676
type_imports=cst.Name("Optional, List"),
677
class_name=cst.Name("DataProcessor"),
678
init_params=cst.Parameters([
679
cst.Param(cst.Name("data"), cst.Annotation(cst.Name("List[str]")))
680
]),
681
init_body=cst.SimpleStatementLine([
682
cst.Assign([cst.AssignTarget(cst.Attribute(cst.Name("self"), cst.Name("data")))], cst.Name("data"))
683
]),
684
method_name=cst.Name("process"),
685
return_type=cst.Name("Optional[str]"),
686
method_body=cst.SimpleStatementLine([
687
cst.Return(cst.Name("None"))
688
])
689
)
690
691
# Add copyright header
692
final_module = insert_header_comments(module, [
693
"# Copyright 2024 Example Corp",
694
"# Auto-generated code - do not edit manually"
695
])
696
697
return final_module.code
698
699
if __name__ == "__main__":
700
# Generate and print example code
701
generated_code = generate_boilerplate_code()
702
print("Generated Code:")
703
print(generated_code)
704
705
# Calculate module information
706
module_info = calculate_module_and_package(
707
repo_root="/project/root",
708
filename="/project/root/src/generated/processor.py"
709
)
710
print(f"\nModule: {module_info.name}")
711
print(f"Package: {module_info.package}")
712
```
713
714
### Advanced Template Usage
715
716
```python
717
from libcst.helpers import parse_template_statement, parse_template_expression
718
719
def create_error_handling_wrapper():
720
"""Create error handling code using templates"""
721
722
# Template for try-catch wrapper
723
try_template = """
724
try:
725
{original_call}
726
except {exception_type} as e:
727
{error_handler}
728
"""
729
730
# Create the wrapped statement
731
wrapped = parse_template_statement(
732
try_template,
733
original_call=parse_template_expression("process_data(input_value)"),
734
exception_type=cst.Name("ValueError"),
735
error_handler=parse_template_statement(
736
'logger.error("Processing failed: %s", str(e))'
737
)
738
)
739
740
return wrapped
741
```
742
743
This comprehensive utilities documentation provides developers with all the tools they need to work effectively with LibCST, from basic node manipulation to advanced code generation and testing workflows.