0
# Plugin Development Framework
1
2
Complete framework for developing custom refurb checks with AST analysis utilities, type checking helpers, and visitor patterns.
3
4
## Capabilities
5
6
### Core Plugin Architecture
7
8
The visitor system that enables custom checks to be seamlessly integrated into refurb's analysis pipeline.
9
10
```python { .api }
11
class RefurbVisitor:
12
"""
13
Main visitor class that traverses AST nodes and runs applicable checks.
14
15
The visitor implements the standard visitor pattern for Mypy AST nodes,
16
dispatching to registered checks based on node type.
17
18
Attributes:
19
- checks: defaultdict[type[Node], list[Check]] - Checks organized by AST node type
20
- settings: Settings - Configuration controlling analysis behavior
21
- errors: list[Error] - Accumulated errors found during traversal
22
"""
23
24
def __init__(self, checks: defaultdict[type[Node], list[Check]], settings: Settings):
25
"""
26
Initialize visitor with checks and settings.
27
28
Parameters:
29
- checks: Dictionary mapping AST node types to lists of applicable checks
30
- settings: Configuration object controlling analysis behavior
31
"""
32
33
def run_check(self, node: Node, check: Check) -> None:
34
"""
35
Execute a single check on an AST node.
36
37
Handles both 2-parameter and 3-parameter check function signatures,
38
manages error collection, and provides error context.
39
40
Parameters:
41
- node: AST node to analyze
42
- check: Check function to execute
43
"""
44
45
def visit_call_expr(self, o: CallExpr) -> None:
46
"""
47
Visit function call expressions and run applicable checks.
48
49
Example of node-specific visitor method. Similar methods exist for
50
all supported AST node types.
51
52
Parameters:
53
- o: CallExpr AST node to analyze
54
"""
55
56
class TraverserVisitor:
57
"""
58
Base AST traversal visitor providing infrastructure for walking AST trees.
59
60
Provides the foundation for RefurbVisitor with automatic traversal
61
and node dispatch capabilities.
62
"""
63
```
64
65
### Check Loading System
66
67
Functions that discover, load, and organize checks from built-in modules and plugins.
68
69
```python { .api }
70
def load_checks(settings: Settings) -> defaultdict[type[Node], list[Check]]:
71
"""
72
Load and filter checks based on settings configuration.
73
74
Discovers checks from:
75
1. Built-in check modules in refurb.checks.*
76
2. Plugin entry points (refurb.plugins group)
77
3. Additional modules specified via --load
78
79
Applies enable/disable filtering based on settings.
80
81
Parameters:
82
- settings: Configuration specifying which checks to load and filter rules
83
84
Returns:
85
Dictionary mapping AST node types to lists of applicable check functions
86
"""
87
88
def get_modules(paths: list[str]) -> Generator[ModuleType, None, None]:
89
"""
90
Load Python modules from specified paths.
91
92
Searches for check modules in:
93
- Built-in refurb.checks package hierarchy
94
- Plugin entry points
95
- Custom paths specified via --load
96
97
Parameters:
98
- paths: List of module paths or directory paths to search
99
100
Yields:
101
Python module objects containing check definitions
102
"""
103
104
def get_error_class(module: ModuleType) -> type[Error] | None:
105
"""
106
Extract Error class from a check module.
107
108
Looks for classes inheriting from Error that define check metadata
109
like error codes, categories, and default enabled status.
110
111
Parameters:
112
- module: Python module to examine
113
114
Returns:
115
Error class if found, None if module doesn't define checks
116
"""
117
118
def should_load_check(settings: Settings, error: type[Error]) -> bool:
119
"""
120
Determine if a check should be enabled based on settings.
121
122
Applies filtering logic considering:
123
- Default enabled status of the check
124
- Explicit enable/disable settings
125
- enable_all/disable_all global flags
126
- Category-based filtering rules
127
128
Parameters:
129
- settings: Configuration containing enable/disable rules
130
- error: Error class representing the check
131
132
Returns:
133
True if check should be loaded and enabled, False otherwise
134
"""
135
```
136
137
### Plugin Entry Points
138
139
Refurb discovers plugins through Python entry points:
140
141
```python
142
# setup.py or pyproject.toml entry point definition
143
[project.entry-points."refurb.plugins"]
144
my_plugin = "my_plugin.checks"
145
146
# Plugin module structure
147
# my_plugin/checks.py
148
from refurb.error import Error
149
150
class MyCustomError(Error):
151
enabled = True
152
name = "Use better pattern"
153
prefix = "PLUG" # Custom prefix
154
categories = {"readability"}
155
code = 1
156
157
def check(node: CallExpr, errors: list[Error]) -> None:
158
"""Check function that will be automatically discovered."""
159
if should_flag_pattern(node):
160
errors.append(MyCustomError(
161
line=node.line,
162
column=node.column,
163
msg="Consider using better_function() instead"
164
))
165
```
166
167
### Check Function Signatures
168
169
Refurb supports multiple check function signatures for flexibility:
170
171
```python { .api }
172
# Type definitions for check functions
173
Check = Callable[[Node, list[Error]], None] | Callable[[Node, list[Error], Settings], None]
174
175
# Two-parameter signature (most common)
176
def check_pattern(node: Node, errors: list[Error]) -> None:
177
"""Simple check function."""
178
179
# Three-parameter signature (access to settings)
180
def check_with_settings(node: Node, errors: list[Error], settings: Settings) -> None:
181
"""Check function with access to configuration."""
182
if settings.verbose:
183
# More detailed analysis in verbose mode
184
pass
185
```
186
187
### Visitor Node Mappings
188
189
Comprehensive mapping of visitor methods to AST node types for precise targeting of checks.
190
191
```python { .api }
192
METHOD_NODE_MAPPINGS: dict[str, type[Node]]
193
"""
194
Dictionary mapping visitor method names to AST node types.
195
196
Contains 89 different AST node type mappings, enabling checks to target
197
specific language constructs:
198
199
- visit_call_expr -> CallExpr (function calls)
200
- visit_name_expr -> NameExpr (variable references)
201
- visit_member_expr -> MemberExpr (attribute access)
202
- visit_op_expr -> OpExpr (binary operations)
203
- visit_comparison_expr -> ComparisonExpr (comparisons)
204
- visit_if_stmt -> IfStmt (if statements)
205
- visit_for_stmt -> ForStmt (for loops)
206
- visit_while_stmt -> WhileStmt (while loops)
207
- visit_try_stmt -> TryStmt (try/except blocks)
208
- visit_with_stmt -> WithStmt (with statements)
209
- ... and many more
210
"""
211
```
212
213
### Plugin Development Examples
214
215
```python
216
# Example: Custom check for deprecated function usage
217
from mypy.nodes import CallExpr, MemberExpr, NameExpr
218
from refurb.error import Error
219
220
class DeprecatedFunctionError(Error):
221
"""Error for deprecated function usage."""
222
enabled = True
223
name = "Avoid deprecated function"
224
prefix = "PLUG"
225
categories = {"modernization"}
226
code = 100
227
228
def check_deprecated_calls(node: CallExpr, errors: list[Error]) -> None:
229
"""Check for calls to deprecated functions."""
230
if isinstance(node.callee, NameExpr):
231
if node.callee.name in {"deprecated_func", "old_api"}:
232
errors.append(DeprecatedFunctionError(
233
line=node.line,
234
column=node.column,
235
msg=f"Replace `{node.callee.name}()` with modern alternative"
236
))
237
238
# Example: Check with settings access
239
def check_with_config(node: Node, errors: list[Error], settings: Settings) -> None:
240
"""Check that can access configuration settings."""
241
if settings.python_version and settings.python_version >= (3, 10):
242
# Only apply this check for Python 3.10+
243
check_modern_syntax(node, errors)
244
245
# Example: Multi-node type check
246
def check_multiple_patterns(node: Node, errors: list[Error]) -> None:
247
"""Check that handles multiple AST node types."""
248
if isinstance(node, (CallExpr, MemberExpr)):
249
# Handle both function calls and attribute access
250
analyze_pattern(node, errors)
251
```
252
253
### Check Generation Utilities
254
255
Refurb provides utilities for generating boilerplate code for new checks, accessed via the `gen` subcommand.
256
257
```python { .api }
258
def main() -> None:
259
"""
260
Interactive generator for creating new check boilerplate.
261
262
This function:
263
1. Prompts user to select AST node types to handle
264
2. Prompts for target file path and error prefix
265
3. Generates complete check file with proper imports and structure
266
4. Creates necessary __init__.py files in parent directories
267
5. Automatically assigns next available error ID
268
269
Accessed via: refurb gen
270
"""
271
272
def get_next_error_id(prefix: str) -> int:
273
"""
274
Find the next available error ID for a given prefix.
275
276
Parameters:
277
- prefix: Error code prefix (e.g., "FURB", "PLUG")
278
279
Returns:
280
Next unused error ID number for the prefix
281
"""
282
283
def build_imports(names: list[str]) -> str:
284
"""
285
Generate import statements for selected AST node types.
286
287
Parameters:
288
- names: List of AST node type names (e.g., ["CallExpr", "NameExpr"])
289
290
Returns:
291
Formatted import statements organized by module
292
"""
293
```
294
295
### Plugin Testing
296
297
```python
298
# Test infrastructure for plugin development
299
from refurb.main import run_refurb
300
from refurb.settings import Settings
301
302
def test_my_check():
303
"""Test custom check behavior."""
304
settings = Settings(
305
files=["test_file.py"],
306
load=["my_plugin"] # Load custom plugin
307
)
308
309
errors = run_refurb(settings)
310
311
# Verify expected errors are found
312
assert any(error.prefix == "PLUG" and error.code == 100 for error in errors)
313
```
314
315
### Advanced Plugin Features
316
317
```python
318
# Plugin with custom categories
319
class AdvancedError(Error):
320
enabled = False # Disabled by default
321
name = "Advanced pattern check"
322
prefix = "ADV"
323
categories = {"performance", "security"} # Multiple categories
324
code = 1
325
326
# Plugin with path-specific behavior
327
def check_with_path_logic(node: Node, errors: list[Error], settings: Settings) -> None:
328
"""Check that behaves differently based on file path."""
329
if node.get_line() and "test" in (node.get_line().source_file or ""):
330
# Different behavior in test files
331
return
332
333
# Normal checking logic
334
standard_analysis(node, errors)
335
336
# Plugin integration with amendment rules
337
# In pyproject.toml:
338
# [[tool.refurb.amend]]
339
# path = "legacy/"
340
# ignore = ["ADV001"] # Ignore advanced checks in legacy code
341
```
342
343
### Node Type Coverage
344
345
Refurb supports checks for all major Python language constructs:
346
347
- **Expressions**: CallExpr, NameExpr, MemberExpr, OpExpr, ComparisonExpr, etc.
348
- **Statements**: IfStmt, ForStmt, WhileStmt, TryStmt, WithStmt, etc.
349
- **Definitions**: FuncDef, ClassDef, VarDef, etc.
350
- **Literals**: IntExpr, StrExpr, FloatExpr, etc.
351
- **Complex constructs**: ListExpr, DictExpr, SetExpr, TupleExpr, etc.
352
353
This comprehensive coverage enables plugins to analyze virtually any Python code pattern.