0
# AST Analysis Utilities
1
2
Comprehensive utilities for AST node analysis, type checking, and code pattern matching used by built-in checks and available for custom plugin development.
3
4
## Capabilities
5
6
### Core AST Analysis Functions
7
8
Fundamental functions for comparing and analyzing AST nodes that form the foundation of check logic.
9
10
```python { .api }
11
def is_equivalent(lhs: Node | None, rhs: Node | None) -> bool:
12
"""
13
Compare two AST nodes for structural equivalence.
14
15
Performs deep comparison of AST structure, ignoring location information
16
but comparing all semantic content including names, literals, operators,
17
and nested structures.
18
19
Parameters:
20
- lhs: First AST node to compare (None allowed)
21
- rhs: Second AST node to compare (None allowed)
22
23
Returns:
24
True if nodes are structurally equivalent, False otherwise
25
26
Examples:
27
- is_equivalent(NameExpr("x"), NameExpr("x")) -> True
28
- is_equivalent(IntExpr(42), IntExpr(42)) -> True
29
- is_equivalent(CallExpr("f", []), CallExpr("g", [])) -> False
30
"""
31
32
def stringify(node: Node) -> str:
33
"""
34
Convert AST node to string representation of equivalent source code.
35
36
Reconstructs readable Python source code from AST nodes, handling
37
all expression types, operators, literals, and complex structures.
38
39
Parameters:
40
- node: AST node to convert to string
41
42
Returns:
43
String representation of the code that would generate this AST node
44
45
Examples:
46
- stringify(NameExpr("variable")) -> "variable"
47
- stringify(CallExpr("func", [IntExpr(1)])) -> "func(1)"
48
- stringify(OpExpr("+", NameExpr("x"), IntExpr(5))) -> "x + 5"
49
"""
50
```
51
52
### Type Analysis Functions
53
54
Functions that leverage Mypy's type system for sophisticated type-based analysis.
55
56
```python { .api }
57
def get_mypy_type(node: Node) -> Type | SymbolNode | None:
58
"""
59
Extract Mypy type information from AST node.
60
61
Retrieves the resolved type information that Mypy has computed for
62
an AST node, enabling type-aware analysis and checks.
63
64
Parameters:
65
- node: AST node to get type information for
66
67
Returns:
68
Mypy Type object, SymbolNode, or None if type unavailable
69
70
Examples:
71
- get_mypy_type(IntExpr(42)) -> Instance(int)
72
- get_mypy_type(NameExpr("x")) -> Type of variable x
73
- get_mypy_type(CallExpr(...)) -> Return type of function call
74
"""
75
76
def is_same_type(ty: Type | SymbolNode | None, *expected: TypeLike) -> bool:
77
"""
78
Check if a type matches any of the expected types.
79
80
Compares resolved Mypy types against expected type patterns,
81
supporting both exact matches and inheritance relationships.
82
83
Parameters:
84
- ty: Type to check (from get_mypy_type)
85
- expected: Variable number of expected type patterns
86
87
Returns:
88
True if type matches any expected pattern, False otherwise
89
90
Examples:
91
- is_same_type(int_type, "builtins.int") -> True
92
- is_same_type(list_type, "builtins.list") -> True
93
- is_same_type(custom_type, "my.module.MyClass") -> True
94
"""
95
```
96
97
### Pattern Matching Utilities
98
99
Specialized functions for extracting and analyzing common code patterns.
100
101
```python { .api }
102
def extract_binary_oper(oper: str, node: OpExpr) -> tuple[Expression, Expression] | None:
103
"""
104
Extract operands from binary operation if it matches expected operator.
105
106
Parameters:
107
- oper: Expected operator string ("+", "-", "*", "/", etc.)
108
- node: Binary operation AST node
109
110
Returns:
111
Tuple of (left_operand, right_operand) if operator matches, None otherwise
112
113
Examples:
114
- extract_binary_oper("+", x_plus_y_node) -> (x_expr, y_expr)
115
- extract_binary_oper("*", x_plus_y_node) -> None
116
"""
117
118
def get_common_expr_positions(*exprs: Expression) -> tuple[int, int] | None:
119
"""
120
Find common source position range across multiple expressions.
121
122
Useful for generating error messages that span multiple related expressions.
123
124
Parameters:
125
- exprs: Variable number of Expression nodes
126
127
Returns:
128
Tuple of (start_position, end_position) or None if no common range
129
"""
130
131
def get_fstring_parts(expr: Expression) -> list[tuple[bool, Expression, str]]:
132
"""
133
Parse f-string expression into component parts.
134
135
Extracts literal text and embedded expressions from f-string literals
136
for detailed analysis of string formatting patterns.
137
138
Parameters:
139
- expr: F-string expression to parse
140
141
Returns:
142
List of tuples: (is_expression, ast_node, text_content)
143
- is_expression: True for {expr} parts, False for literal text
144
- ast_node: AST node for the part
145
- text_content: String representation
146
"""
147
```
148
149
### Code Usage Analysis
150
151
Functions for analyzing variable usage patterns and code context.
152
153
```python { .api }
154
class ReadCountVisitor:
155
"""
156
AST visitor that counts variable usage within code contexts.
157
158
Tracks how many times variables are read, written, or referenced
159
within specified code blocks, enabling dead code detection and
160
usage pattern analysis.
161
162
Attributes:
163
- read_count: dict[str, int] - Count of variable reads by name
164
- contexts: list[Node] - Code contexts being analyzed
165
"""
166
167
def visit_name_expr(self, node: NameExpr) -> None:
168
"""Count name expression reads."""
169
170
def get_read_count(self, name: str) -> int:
171
"""Get total read count for variable name."""
172
173
def is_name_unused_in_contexts(name: NameExpr, contexts: list[Node]) -> bool:
174
"""
175
Check if a variable name is unused within specified code contexts.
176
177
Uses ReadCountVisitor to analyze variable usage patterns and identify
178
potentially dead or redundant variable assignments.
179
180
Parameters:
181
- name: Variable name expression to check
182
- contexts: List of AST nodes representing code contexts to search
183
184
Returns:
185
True if variable is never read in any context, False otherwise
186
"""
187
```
188
189
### Type Checking Predicates
190
191
Functions that identify specific types and patterns in AST nodes.
192
193
```python { .api }
194
def is_none_literal(node: Node) -> TypeGuard[NameExpr]:
195
"""
196
Check if AST node represents the None literal.
197
198
Parameters:
199
- node: AST node to check
200
201
Returns:
202
True if node is None literal, False otherwise
203
"""
204
205
def is_bool_literal(node: Node) -> TypeGuard[NameExpr]:
206
"""
207
Check if AST node represents a boolean literal (True or False).
208
209
Parameters:
210
- node: AST node to check
211
212
Returns:
213
True if node is True or False literal, False otherwise
214
"""
215
216
def is_mapping(expr: Expression) -> bool:
217
"""
218
Check if expression has mapping (dict-like) type.
219
220
Uses Mypy type information to determine if expression implements
221
the mapping protocol (dict, defaultdict, etc.).
222
223
Parameters:
224
- expr: Expression to check
225
226
Returns:
227
True if expression is mapping type, False otherwise
228
"""
229
230
def is_sized(node: Expression) -> bool:
231
"""
232
Check if expression has sized type (implements __len__).
233
234
Parameters:
235
- node: Expression to check
236
237
Returns:
238
True if expression implements sized protocol, False otherwise
239
"""
240
```
241
242
### Path and Module Utilities
243
244
Functions for handling filesystem paths and module names in cross-platform analysis.
245
246
```python { .api }
247
def normalize_os_path(module: str | None) -> str:
248
"""
249
Normalize module path for cross-platform compatibility.
250
251
Converts module paths to standardized format, handling differences
252
between Windows and Unix-style paths in import analysis.
253
254
Parameters:
255
- module: Module path string to normalize
256
257
Returns:
258
Normalized path string suitable for cross-platform comparison
259
"""
260
```
261
262
### Usage Examples
263
264
```python
265
from refurb.checks.common import (
266
is_equivalent, stringify, get_mypy_type, is_same_type,
267
extract_binary_oper, is_none_literal, ReadCountVisitor
268
)
269
from mypy.nodes import CallExpr, NameExpr, OpExpr
270
271
def custom_check(node: CallExpr, errors: list[Error]) -> None:
272
"""Example custom check using AST utilities."""
273
274
# Check if this is a call to 'len' function
275
if isinstance(node.callee, NameExpr) and node.callee.name == "len":
276
arg = node.args[0]
277
278
# Get type information
279
arg_type = get_mypy_type(arg)
280
281
# Check for list type specifically
282
if is_same_type(arg_type, "builtins.list"):
283
# Suggest using collection directly in boolean context
284
suggestion = f"Use `{stringify(arg)}` directly in boolean context"
285
errors.append(MyError(node.line, node.column, suggestion))
286
287
def analyze_binary_ops(node: OpExpr, errors: list[Error]) -> None:
288
"""Example using pattern matching utilities."""
289
290
# Check for x + 0 pattern
291
if operands := extract_binary_oper("+", node):
292
left, right = operands
293
294
# Check if right operand is zero
295
if isinstance(right, IntExpr) and right.value == 0:
296
# Suggest removing redundant addition
297
replacement = stringify(left)
298
errors.append(RedundantOpError(
299
node.line, node.column,
300
f"Replace `{stringify(node)}` with `{replacement}`"
301
))
302
303
def check_unused_variables(context: Node, errors: list[Error]) -> None:
304
"""Example using usage analysis."""
305
306
visitor = ReadCountVisitor()
307
context.accept(visitor)
308
309
# Find variables that are never read
310
for var_name, count in visitor.read_count.items():
311
if count == 0:
312
errors.append(UnusedVariableError(
313
context.line, context.column,
314
f"Variable '{var_name}' is never used"
315
))
316
```
317
318
### Integration with Built-in Checks
319
320
These utilities are extensively used throughout refurb's 94 built-in checks:
321
322
- **Type-based checks**: Use `get_mypy_type` and `is_same_type` for sophisticated type analysis
323
- **Pattern matching**: Use `extract_binary_oper` and equivalence checking for code patterns
324
- **Code generation**: Use `stringify` for generating replacement suggestions
325
- **Usage analysis**: Use `ReadCountVisitor` for dead code detection
326
- **Cross-platform support**: Use path normalization for consistent behavior
327
328
The utilities provide a solid foundation for both built-in checks and custom plugin development, abstracting away the complexity of AST manipulation and type analysis.