or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

ast-utilities.mddocs/

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.