or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-visitor.mdbadge-generation.mdcli-interface.mdconfiguration.mdcoverage-analysis.mdindex.mdutilities.md

ast-visitor.mddocs/

0

# AST Visitor

1

2

AST (Abstract Syntax Tree) visitor functionality for traversing Python source code and collecting docstring coverage information. The visitor pattern is used to examine Python code structure and identify missing docstrings across modules, classes, functions, and methods.

3

4

## Capabilities

5

6

### Coverage Node Data Structure

7

8

Data structure representing coverage information for individual code elements in the AST.

9

10

```python { .api }

11

class CovNode:

12

"""Coverage information for an AST node."""

13

14

name: str # Name of the code element

15

path: str # File path containing the element

16

level: int # Nesting level (0 = module, 1 = class/function, etc.)

17

lineno: int # Line number in source file

18

covered: bool # Whether element has docstring

19

node_type: str # Type of AST node ("module", "class", "function", "method")

20

is_nested_func: bool # Whether function is nested inside another function

21

is_nested_cls: bool # Whether class is nested inside another class

22

parent: object # Parent node (for nested elements)

23

```

24

25

### AST Coverage Visitor

26

27

The main visitor class that traverses Python AST to collect docstring coverage information.

28

29

```python { .api }

30

class CoverageVisitor:

31

"""AST visitor for collecting docstring coverage data."""

32

33

def __init__(self, filename, config):

34

"""

35

Initialize AST visitor.

36

37

Args:

38

filename: Path to file being analyzed

39

config: InterrogateConfig with analysis options

40

"""

41

42

def visit_Module(self, node):

43

"""Visit module-level node."""

44

45

def visit_ClassDef(self, node):

46

"""Visit class definition node."""

47

48

def visit_FunctionDef(self, node):

49

"""Visit function definition node."""

50

51

def visit_AsyncFunctionDef(self, node):

52

"""Visit async function definition node."""

53

```

54

55

### Node Analysis Helpers

56

57

Static and helper methods for analyzing AST nodes and determining coverage rules.

58

59

```python { .api }

60

class CoverageVisitor:

61

@staticmethod

62

def _has_doc(node):

63

"""

64

Check if an AST node has a docstring.

65

66

Args:

67

node: AST node to check

68

69

Returns:

70

bool: True if node has docstring

71

"""

72

73

def _is_nested_func(self, parent, node_type):

74

"""

75

Determine if function is nested inside another function.

76

77

Args:

78

parent: Parent AST node

79

node_type: Type of current node

80

81

Returns:

82

bool: True if function is nested

83

"""

84

85

def _is_nested_cls(self, parent, node_type):

86

"""

87

Determine if class is nested inside another class.

88

89

Args:

90

parent: Parent AST node

91

node_type: Type of current node

92

93

Returns:

94

bool: True if class is nested

95

"""

96

97

def _is_private(self, node):

98

"""

99

Check if node represents a private method/function.

100

101

Args:

102

node: AST node to check

103

104

Returns:

105

bool: True if node is private (starts with __)

106

"""

107

108

def _is_semiprivate(self, node):

109

"""

110

Check if node represents a semiprivate method/function.

111

112

Args:

113

node: AST node to check

114

115

Returns:

116

bool: True if node is semiprivate (starts with single _)

117

"""

118

119

def _has_property_decorators(self, node):

120

"""

121

Check if function has property decorators.

122

123

Args:

124

node: Function AST node

125

126

Returns:

127

bool: True if has @property, @setter, @getter, or @deleter

128

"""

129

130

def _has_setters(self, node):

131

"""

132

Check if function has property setter decorators.

133

134

Args:

135

node: Function AST node

136

137

Returns:

138

bool: True if has @property.setter decorator

139

"""

140

141

def _has_overload_decorator(self, node):

142

"""

143

Check if function has @typing.overload decorator.

144

145

Args:

146

node: Function AST node

147

148

Returns:

149

bool: True if has @overload decorator

150

"""

151

```

152

153

## Usage Examples

154

155

### Basic AST Analysis

156

157

```python

158

import ast

159

from interrogate.visit import CoverageVisitor

160

from interrogate.config import InterrogateConfig

161

162

# Parse Python source code

163

source_code = '''

164

class MyClass:

165

"""Class docstring."""

166

167

def method_with_doc(self):

168

"""Method with docstring."""

169

pass

170

171

def method_without_doc(self):

172

pass

173

'''

174

175

# Create AST and visitor

176

tree = ast.parse(source_code)

177

config = InterrogateConfig()

178

visitor = CoverageVisitor("example.py", config)

179

180

# Visit all nodes

181

visitor.visit(tree)

182

183

# Access collected coverage nodes

184

for node in visitor.covered_nodes:

185

print(f"{node.name} ({node.node_type}): {'COVERED' if node.covered else 'MISSING'}")

186

```

187

188

### Custom Configuration Analysis

189

190

```python

191

import ast

192

from interrogate.visit import CoverageVisitor

193

from interrogate.config import InterrogateConfig

194

195

# Configure analysis options

196

config = InterrogateConfig(

197

ignore_private=True,

198

ignore_magic=True,

199

ignore_init_method=True

200

)

201

202

source = '''

203

class Example:

204

def __init__(self):

205

pass

206

207

def _private_method(self):

208

pass

209

210

def __magic_method__(self):

211

pass

212

213

def public_method(self):

214

pass

215

'''

216

217

tree = ast.parse(source)

218

visitor = CoverageVisitor("example.py", config)

219

visitor.visit(tree)

220

221

# Only public_method should be analyzed due to ignore settings

222

for node in visitor.covered_nodes:

223

if not node.covered:

224

print(f"Missing docstring: {node.name}")

225

```

226

227

### Nested Structure Analysis

228

229

```python

230

import ast

231

from interrogate.visit import CoverageVisitor, CovNode

232

from interrogate.config import InterrogateConfig

233

234

source = '''

235

class OuterClass:

236

"""Outer class docstring."""

237

238

class InnerClass:

239

def inner_method(self):

240

def nested_function():

241

pass

242

pass

243

244

def outer_method(self):

245

def local_function():

246

pass

247

pass

248

'''

249

250

tree = ast.parse(source)

251

config = InterrogateConfig()

252

visitor = CoverageVisitor("nested_example.py", config)

253

visitor.visit(tree)

254

255

# Analyze nested structure

256

for node in visitor.covered_nodes:

257

indent = " " * node.level

258

nested_info = []

259

if node.is_nested_cls:

260

nested_info.append("nested class")

261

if node.is_nested_func:

262

nested_info.append("nested function")

263

264

nested_str = f" ({', '.join(nested_info)})" if nested_info else ""

265

print(f"{indent}{node.name} ({node.node_type}){nested_str}: line {node.lineno}")

266

```

267

268

### Decorator Detection

269

270

```python

271

import ast

272

from interrogate.visit import CoverageVisitor

273

from interrogate.config import InterrogateConfig

274

275

source = '''

276

from typing import overload

277

278

class Properties:

279

@property

280

def value(self):

281

return self._value

282

283

@value.setter

284

def value(self, val):

285

self._value = val

286

287

@overload

288

def process(self, x: int) -> int: ...

289

290

@overload

291

def process(self, x: str) -> str: ...

292

293

def process(self, x):

294

return x

295

'''

296

297

tree = ast.parse(source)

298

visitor = CoverageVisitor("decorators.py", InterrogateConfig())

299

visitor.visit(tree)

300

301

# Check decorator detection

302

for node in visitor.covered_nodes:

303

if node.node_type in ("function", "method"):

304

# Access private methods to check decorator status

305

ast_node = node._ast_node # Hypothetical access to original AST node

306

307

if visitor._has_property_decorators(ast_node):

308

print(f"{node.name}: Has property decorators")

309

if visitor._has_setters(ast_node):

310

print(f"{node.name}: Has setter decorators")

311

if visitor._has_overload_decorator(ast_node):

312

print(f"{node.name}: Has overload decorator")

313

```

314

315

### Integration with Coverage Analysis

316

317

```python

318

import ast

319

from interrogate.visit import CoverageVisitor

320

from interrogate.config import InterrogateConfig

321

from interrogate.coverage import InterrogateFileResult

322

323

def analyze_file_ast(filename, config):

324

"""Analyze a Python file's AST for docstring coverage."""

325

326

with open(filename, 'r') as f:

327

source = f.read()

328

329

try:

330

tree = ast.parse(source)

331

except SyntaxError as e:

332

print(f"Syntax error in {filename}: {e}")

333

return None

334

335

# Create visitor and analyze

336

visitor = CoverageVisitor(filename, config)

337

visitor.visit(tree)

338

339

# Create file result

340

file_result = InterrogateFileResult(

341

filename=filename,

342

nodes=visitor.covered_nodes

343

)

344

file_result.combine() # Calculate totals

345

346

return file_result

347

348

# Usage

349

config = InterrogateConfig(fail_under=80.0)

350

result = analyze_file_ast("my_module.py", config)

351

352

if result:

353

print(f"Coverage: {result.perc_covered:.1f}%")

354

print(f"Missing: {result.missing}/{result.total}")

355

```

356

357

### Custom Node Filtering

358

359

```python

360

from interrogate.visit import CoverageVisitor

361

from interrogate.config import InterrogateConfig

362

363

class CustomCoverageVisitor(CoverageVisitor):

364

"""Extended visitor with custom filtering logic."""

365

366

def _is_ignored_common(self, node):

367

"""Override common ignore logic."""

368

# Call parent implementation

369

if super()._is_ignored_common(node):

370

return True

371

372

# Add custom logic - ignore test methods

373

if hasattr(node, 'name') and node.name.startswith('test_'):

374

return True

375

376

# Ignore methods with specific decorators

377

if hasattr(node, 'decorator_list'):

378

decorator_names = [d.id for d in node.decorator_list if hasattr(d, 'id')]

379

if 'skip_coverage' in decorator_names:

380

return True

381

382

return False

383

384

# Usage with custom visitor

385

source = '''

386

class TestClass:

387

def test_something(self):

388

"""This will be ignored due to test_ prefix."""

389

pass

390

391

@skip_coverage

392

def skip_this(self):

393

"""This will be ignored due to decorator."""

394

pass

395

396

def normal_method(self):

397

"""This will be analyzed normally."""

398

pass

399

'''

400

401

import ast

402

tree = ast.parse(source)

403

config = InterrogateConfig()

404

visitor = CustomCoverageVisitor("test.py", config)

405

visitor.visit(tree)

406

407

# Only normal_method should be in results

408

for node in visitor.covered_nodes:

409

print(f"Analyzed: {node.name}")

410

```