or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analysis.mdast-utilities.mdcli.mdconfiguration.mderrors.mdindex.mdplugins.md

plugins.mddocs/

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.