or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

checker-system.mdcore-api.mdindex.mdmessage-types.mdreporter-system.md

checker-system.mddocs/

0

# Checker System

1

2

Advanced code analysis engine that performs AST-based static analysis with scope tracking and binding management. The Checker class provides fine-grained control over the analysis process and access to detailed results, supporting custom analysis workflows and integration with development tools.

3

4

The Checker class is the core of pyflakes' analysis engine, implementing a comprehensive visitor pattern for Python AST nodes with sophisticated scope and binding management.

5

6

## Capabilities

7

8

### Core Checker Class

9

10

Main analysis engine that walks Python AST nodes and maintains scope information to detect various code issues.

11

12

```python { .api }

13

class Checker:

14

"""I check the cleanliness and sanity of Python code."""

15

16

def __init__(self, tree, filename='(none)', builtins=None, withDoctest='PYFLAKES_DOCTEST' in os.environ, file_tokens=()):

17

"""

18

Initialize the checker with an AST tree.

19

20

Parameters:

21

- tree: AST tree from ast.parse()

22

- filename (str): Name of the file being checked (for error reporting)

23

- builtins (set, optional): Additional builtin names to recognize (extends default set)

24

- withDoctest (bool): Whether to check doctest code blocks (controlled by PYFLAKES_DOCTEST env var)

25

- file_tokens (tuple): Deprecated parameter for token information (warns when used)

26

"""

27

28

# Core attributes

29

messages: list # List of Message objects representing found issues

30

filename: str # Filename being checked

31

deadScopes: list # List of completed scopes for analysis

32

scopeStack: list # Stack of active scopes during traversal

33

exceptHandlers: list # Stack of exception handler names for context

34

root # Root AST node

35

nodeDepth: int # Current depth in AST traversal

36

offset # Line/column offset for node adjustment

37

withDoctest: bool # Whether doctest processing is enabled

38

39

# Properties

40

@property

41

def futuresAllowed(self) -> bool:

42

"""Whether __future__ imports are allowed at current position."""

43

44

@property

45

def annotationsFutureEnabled(self) -> bool:

46

"""Whether 'from __future__ import annotations' is active."""

47

48

@property

49

def scope(self):

50

"""Current scope (last item in scopeStack)."""

51

52

@property

53

def _in_postponed_annotation(self) -> bool:

54

"""Whether currently in a postponed annotation context."""

55

```

56

57

**Usage:**

58

59

```python

60

import ast

61

import pyflakes.checker

62

63

# Parse Python code into AST

64

code = """

65

import os

66

unused_var = 42

67

print(undefined_var)

68

"""

69

70

tree = ast.parse(code, filename='example.py')

71

checker = pyflakes.checker.Checker(tree, filename='example.py')

72

73

# Access results

74

print(f"Found {len(checker.messages)} issues:")

75

for message in checker.messages:

76

print(f" {message}")

77

78

# Access scope information

79

print(f"Analyzed {len(checker.deadScopes)} scopes")

80

```

81

82

### Core Analysis Methods

83

84

Primary methods for managing the analysis process and reporting issues.

85

86

```python { .api }

87

def report(self, messageClass, *args, **kwargs):

88

"""Add a message to the results."""

89

90

def deferFunction(self, callable):

91

"""Schedule a function handler to be called just before completion."""

92

93

def checkDeadScopes(self):

94

"""Analyze completed scopes for unused names."""

95

96

def handleNode(self, node, parent):

97

"""Process an AST node during analysis."""

98

99

def handleChildren(self, tree, omit=None):

100

"""Process child nodes with optional omissions."""

101

102

def getNodeHandler(self, node_class):

103

"""Get handler method for AST node type."""

104

105

def addBinding(self, node, value):

106

"""Add name binding to current scope."""

107

```

108

109

### Scope Management Methods

110

111

Methods for managing Python scopes and their relationships.

112

113

```python { .api }

114

def in_scope(self, cls):

115

"""Context manager for scope management."""

116

117

def getScopeNode(self, node):

118

"""Find scope-defining ancestor."""

119

120

def getParent(self, node):

121

"""Get meaningful parent of a node."""

122

123

def getCommonAncestor(self, lnode, rnode, stop):

124

"""Find common ancestor of two nodes."""

125

126

def descendantOf(self, node, ancestors, stop):

127

"""Check if node is descendant of ancestors."""

128

129

def differentForks(self, lnode, rnode):

130

"""Check if nodes are on different conditional branches."""

131

```

132

133

### Name Binding Methods

134

135

Methods for handling variable assignments and name resolution.

136

137

```python { .api }

138

def handleNodeLoad(self, node, parent):

139

"""Handle name loading/usage."""

140

141

def handleNodeStore(self, node):

142

"""Handle name storage/assignment."""

143

144

def handleNodeDelete(self, node):

145

"""Handle name deletion."""

146

```

147

148

### Annotation Processing Methods

149

150

Methods for handling type annotations and forward references.

151

152

```python { .api }

153

def handleAnnotation(self, annotation, node):

154

"""Process type annotations."""

155

156

def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err):

157

"""Process string annotations."""

158

159

def handle_annotation_always_deferred(self, annotation, parent):

160

"""Always defer annotation processing."""

161

162

def _enter_annotation(self, ann_type=AnnotationState.BARE):

163

"""Context manager for annotations."""

164

```

165

166

### AST Node Handlers

167

168

The Checker class implements handlers for all Python AST node types. Each handler is named after the AST node type in uppercase.

169

170

#### Statement Handlers

171

172

```python { .api }

173

def FUNCTIONDEF(self, node):

174

"""Handle function definitions."""

175

176

def ASYNCFUNCTIONDEF(self, node):

177

"""Handle async function definitions."""

178

179

def CLASSDEF(self, node):

180

"""Handle class definitions."""

181

182

def RETURN(self, node):

183

"""Handle return statements with scope validation."""

184

185

def YIELD(self, node):

186

"""Handle yield expressions with scope validation."""

187

188

def GLOBAL(self, node):

189

"""Handle global declarations."""

190

191

def IMPORT(self, node):

192

"""Handle import statements."""

193

194

def IMPORTFROM(self, node):

195

"""Handle 'from X import Y' statements."""

196

197

def ASSIGN(self, node):

198

"""Handle assignments."""

199

200

def AUGASSIGN(self, node):

201

"""Handle augmented assignments (+=, etc.)."""

202

203

def ANNASSIGN(self, node):

204

"""Handle annotated assignments."""

205

206

def FOR(self, node):

207

"""Handle for loops."""

208

209

def ASYNCFOR(self, node):

210

"""Handle async for loops."""

211

212

def WHILE(self, node):

213

"""Handle while loops."""

214

215

def IF(self, node):

216

"""Handle if statements with tuple condition checking."""

217

218

def WITH(self, node):

219

"""Handle with statements."""

220

221

def ASYNCWITH(self, node):

222

"""Handle async with statements."""

223

224

def TRY(self, node):

225

"""Handle try/except blocks with handler tracking."""

226

227

def EXCEPTHANDLER(self, node):

228

"""Handle exception handlers."""

229

230

def RAISE(self, node):

231

"""Handle raise statements with NotImplemented checking."""

232

233

def ASSERT(self, node):

234

"""Handle assert statements with tuple condition checking."""

235

236

def CONTINUE(self, node):

237

"""Handle continue/break statements with context validation."""

238

239

def BREAK(self, node):

240

"""Handle break statements."""

241

242

def PASS(self, node):

243

"""Handle pass statements."""

244

```

245

246

#### Expression Handlers

247

248

```python { .api }

249

def NAME(self, node):

250

"""Handle name references (load/store/delete)."""

251

252

def CALL(self, node):

253

"""Handle function calls with format string validation."""

254

255

def SUBSCRIPT(self, node):

256

"""Handle subscript expressions with special typing logic."""

257

258

def ATTRIBUTE(self, node):

259

"""Handle attribute access."""

260

261

def BINOP(self, node):

262

"""Handle binary operations with percent format checking."""

263

264

def COMPARE(self, node):

265

"""Handle comparison operations with literal checking."""

266

267

def TUPLE(self, node):

268

"""Handle tuples with starred expression validation."""

269

270

def LIST(self, node):

271

"""Handle lists."""

272

273

def DICT(self, node):

274

"""Handle dictionaries with duplicate key detection."""

275

276

def SET(self, node):

277

"""Handle sets."""

278

279

def CONSTANT(self, node):

280

"""Handle constants with string annotation support."""

281

282

def JOINEDSTR(self, node):

283

"""Handle f-strings with placeholder validation."""

284

285

def TEMPLATESTR(self, node):

286

"""Handle template strings (t-strings)."""

287

288

def LAMBDA(self, node):

289

"""Handle lambda expressions and function arguments."""

290

291

def GENERATOREXP(self, node):

292

"""Handle generator expressions and comprehensions."""

293

294

def LISTCOMP(self, node):

295

"""Handle list comprehensions."""

296

297

def DICTCOMP(self, node):

298

"""Handle dictionary comprehensions."""

299

300

def SETCOMP(self, node):

301

"""Handle set comprehensions."""

302

```

303

304

#### Type System Handlers (Python 3.12+)

305

306

```python { .api }

307

def TYPEVAR(self, node):

308

"""Handle type variable definitions."""

309

310

def TYPEALIAS(self, node):

311

"""Handle type alias definitions."""

312

313

def PARAMSPEC(self, node):

314

"""Handle parameter specifications."""

315

316

def TYPEVARTUPLE(self, node):

317

"""Handle type variable tuples."""

318

```

319

320

#### Pattern Matching Handlers (Python 3.10+)

321

322

```python { .api }

323

def MATCH(self, node):

324

"""Handle match statements."""

325

326

def MATCH_CASE(self, node):

327

"""Handle match cases."""

328

329

def MATCHAS(self, node):

330

"""Handle match as patterns."""

331

332

def MATCHCLASS(self, node):

333

"""Handle match class patterns."""

334

335

def MATCHMAPPING(self, node):

336

"""Handle match mapping patterns."""

337

338

def MATCHOR(self, node):

339

"""Handle match or patterns."""

340

341

def MATCHSEQUENCE(self, node):

342

"""Handle match sequence patterns."""

343

344

def MATCHSINGLETON(self, node):

345

"""Handle match singleton patterns."""

346

347

def MATCHSTAR(self, node):

348

"""Handle match star patterns."""

349

350

def MATCHVALUE(self, node):

351

"""Handle match value patterns."""

352

```

353

354

### Utility Methods

355

356

Helper methods for common analysis tasks.

357

358

```python { .api }

359

def isLiteralTupleUnpacking(self, node) -> bool:

360

"""Check for literal tuple unpacking."""

361

362

def isDocstring(self, node) -> bool:

363

"""Check if node is a docstring."""

364

365

def getDocstring(self, node):

366

"""Extract docstring content and line number."""

367

368

def handleDoctests(self, node):

369

"""Process doctest examples in docstrings."""

370

371

def ignore(self, node):

372

"""No-op handler for ignored node types."""

373

374

def _in_doctest(self) -> bool:

375

"""Check if currently in doctest scope."""

376

```

377

378

### Format String Validation

379

380

Methods for validating string formatting operations.

381

382

```python { .api }

383

def _handle_string_dot_format(self, node):

384

"""Validate .format() method calls."""

385

386

def _handle_percent_format(self, node):

387

"""Validate % format operations."""

388

```

389

390

## Internal Systems

391

392

### Deferred Analysis System

393

394

The checker implements a deferred analysis system to handle forward references and ensure proper name resolution order.

395

396

```python

397

# Deferred functions are stored and executed after initial traversal

398

checker._deferred = [] # Queue of deferred function handlers

399

checker._run_deferred() # Execute all deferred functions

400

```

401

402

**Key Features:**

403

- Functions bodies are analyzed after global scope is complete

404

- Ensures all global names are visible to function analysis

405

- Preserves scope context when handlers are eventually executed

406

- Critical for handling forward references and decorators

407

408

### Binding Management System

409

410

Comprehensive system for tracking name bindings across all scopes using a hierarchical class system.

411

412

#### Binding Class Hierarchy

413

414

```python { .api }

415

class Binding:

416

"""Base class for all name bindings."""

417

418

def __init__(self, name, source):

419

"""

420

Parameters:

421

- name (str): The bound name

422

- source: AST node where binding occurs

423

"""

424

425

name: str # The bound name

426

source # AST node where binding occurs

427

used # Usage tracking: False or (scope, node) tuple

428

429

def redefines(self, other) -> bool:

430

"""Determines if this binding redefines another."""

431

432

class Definition(Binding):

433

"""Base class for bindings that define functions or classes."""

434

435

def redefines(self, other) -> bool:

436

"""Can redefine other definitions or assignments."""

437

438

class Assignment(Binding):

439

"""Regular variable assignments (x = value)."""

440

441

class NamedExprAssignment(Assignment):

442

"""Walrus operator assignments (x := value)."""

443

444

class Annotation(Binding):

445

"""Type annotations without values (x: int)."""

446

447

def redefines(self, other) -> bool:

448

"""Annotations don't redefine names."""

449

450

class Argument(Binding):

451

"""Function parameters."""

452

453

class FunctionDefinition(Definition):

454

"""Function definitions (def func():)."""

455

456

class ClassDefinition(Definition):

457

"""Class definitions (class MyClass:)."""

458

459

class Builtin(Definition):

460

"""Built-in names (like print, len)."""

461

```

462

463

#### Import-Related Bindings

464

465

```python { .api }

466

class Importation(Definition):

467

"""Standard import statements (import module)."""

468

469

def __init__(self, name, source, full_name=None):

470

"""

471

Parameters:

472

- name (str): Local name for the import

473

- source: AST node

474

- full_name (str): Complete import path

475

"""

476

477

fullName: str # Complete import path

478

redefined: list # List of redefinition nodes

479

480

def _has_alias(self) -> bool:

481

"""Whether import uses 'as' clause."""

482

483

@property

484

def source_statement(self) -> str:

485

"""Reconstructs the original import statement."""

486

487

class SubmoduleImportation(Importation):

488

"""Submodule imports (import package.module)."""

489

490

class ImportationFrom(Importation):

491

"""From imports (from module import name)."""

492

493

class StarImportation(Importation):

494

"""Star imports (from module import *)."""

495

496

class FutureImportation(ImportationFrom):

497

"""Future imports (from __future__ import feature)."""

498

499

class ExportBinding(Binding):

500

"""__all__ assignments for module exports."""

501

502

def __init__(self, name, source, scope):

503

"""Parses __all__ assignments and concatenations."""

504

505

names: list # List of exported names from __all__

506

```

507

508

#### Binding Creation Process

509

510

Bindings are created through several key methods:

511

512

**Name Storage (`handleNodeStore`):**

513

- Creates `Annotation` for type-only annotations (`x: int`)

514

- Creates `Binding` for loop variables and tuple unpacking

515

- Creates `ExportBinding` for `__all__` assignments

516

- Creates `NamedExprAssignment` for walrus operators (`x := value`)

517

- Creates `Assignment` for regular assignments (`x = value`)

518

519

**Import Processing:**

520

- `IMPORT` method creates `Importation` or `SubmoduleImportation`

521

- `IMPORTFROM` method creates `ImportationFrom`, `StarImportation`, or `FutureImportation`

522

523

**Definition Processing:**

524

- `FUNCTIONDEF` creates `FunctionDefinition` bindings

525

- `CLASSDEF` creates `ClassDefinition` bindings

526

- `ARG` creates `Argument` bindings for function parameters

527

528

#### Usage Tracking

529

530

Bindings track their usage through the `used` attribute:

531

- `False` - Never used

532

- `(scope, node)` tuple - Used, with reference to usage location

533

534

Usage is recorded in `handleNodeLoad(node, parent)` when names are accessed.

535

536

### Scope System

537

538

Manages nested Python scopes with their specific rules and behaviors using a hierarchy of scope classes.

539

540

#### Scope Class Hierarchy

541

542

```python { .api }

543

class Scope(dict):

544

"""Base scope class - dictionary of name -> binding."""

545

546

def __init__(self, filename=None):

547

"""Initialize scope with optional filename."""

548

549

def __contains__(self, key):

550

"""Check if name is bound in this scope."""

551

552

def unusedAssignments(self):

553

"""Return unused assignments in this scope."""

554

555

class ModuleScope(Scope):

556

"""Module-level scope."""

557

558

def __init__(self, filename=None):

559

self.importStarred = False # Whether any star imports exist

560

self.futureAnnotations = False # Whether __future__ annotations enabled

561

562

class ClassScope(Scope):

563

"""Class definition scope."""

564

565

def __init__(self, filename=None):

566

# Classes don't participate in normal name resolution

567

pass

568

569

class FunctionScope(Scope):

570

"""Function and method scope."""

571

572

def __init__(self, filename=None):

573

self.globals = {} # Global declarations

574

self.nonlocals = {} # Nonlocal declarations

575

self.indirectGlobals = {} # Names that may become global

576

self.indirectNonlocals = {} # Names that may become nonlocal

577

578

def unused_assignments(self):

579

"""Return unused assignments in function scope."""

580

581

def unused_annotations(self):

582

"""Return unused type annotations in function scope."""

583

584

class GeneratorScope(Scope):

585

"""Generator expressions and comprehensions."""

586

587

def __init__(self, filename=None):

588

# Generator scopes can access class scope variables

589

pass

590

591

class TypeScope(Scope):

592

"""Type parameter scope (Python 3.12+)."""

593

594

class DoctestScope(ModuleScope):

595

"""Doctest execution scope."""

596

```

597

598

#### Scope Stack Management

599

600

The checker maintains a stack of active scopes during AST traversal:

601

602

```python { .api }

603

# Core scope management attributes

604

scopeStack: list # Stack of active scopes during traversal

605

deadScopes: list # List of completed scopes for analysis

606

607

@property

608

def scope(self):

609

"""Current scope (last item in scopeStack)."""

610

return self.scopeStack[-1] if self.scopeStack else None

611

612

def in_scope(self, cls):

613

"""Context manager for scope management."""

614

# Pushes new scope, yields it, then pops and moves to deadScopes

615

```

616

617

#### Scope Resolution Rules

618

619

**Name Lookup Order:**

620

1. Current scope (innermost)

621

2. Enclosing function scopes (if any)

622

3. Global scope (module level)

623

4. Built-in scope

624

625

**Special Scoping Rules:**

626

- **Class scopes** don't participate in normal name resolution for most names

627

- **Generator scopes** can access class variables directly

628

- **Global/nonlocal** declarations affect where names are stored

629

- **Star imports** set `scope.importStarred = True` to disable undefined name warnings

630

631

#### Scope-Binding Integration

632

633

Scopes store bindings and provide methods for analysis:

634

635

```python

636

# Adding bindings to scopes

637

def addBinding(self, node, value):

638

"""Add name binding to appropriate scope."""

639

# Finds correct scope in stack based on global/nonlocal declarations

640

# Handles redefinition checking and reporting

641

# Updates binding usage information

642

643

# Scope analysis after traversal

644

def checkDeadScopes(self):

645

"""Analyze completed scopes for unused names."""

646

# Reports unused imports, variables, and annotations

647

# Validates __all__ exports

648

# Processes star import usage patterns

649

```

650

651

#### Advanced Scope Features

652

653

**Module Scope Features:**

654

- Tracks future imports (`from __future__ import annotations`)

655

- Manages star imports and their effects on undefined name detection

656

- Handles `__all__` export validation

657

658

**Function Scope Features:**

659

- Tracks global and nonlocal declarations

660

- Identifies unused assignments and annotations

661

- Manages function argument bindings

662

- Handles closure variable access

663

664

**Class Scope Features:**

665

- Class variables are not visible in methods (normal Python scoping)

666

- Class scope bindings tracked but don't affect name resolution

667

- Special handling for class decorators and base classes

668

669

**Generator Scope Features:**

670

- Comprehensions create isolated scopes

671

- Can access enclosing class variables (unlike functions)

672

- Iterator variables don't leak to enclosing scope

673

674

### Annotation Processing System

675

676

Handles Python type annotations with support for forward references and `from __future__ import annotations`.

677

678

**Annotation States:**

679

- `BARE` - Direct annotation context

680

- `STRINGIZED` - String annotation context

681

- `POSTPONED` - Deferred annotation context

682

683

**Features:**

684

- String annotation parsing with error handling

685

- Support for `typing` module constructs

686

- Forward reference resolution

687

- Future annotations behavior

688

689

### Message Reporting System

690

691

Structured system for collecting and reporting code issues.

692

693

**Features:**

694

- All messages stored in `messages` list

695

- Detailed location information (file, line, column)

696

- Message classification by severity and type

697

- Integration with reporter system for output formatting

698

699

## Advanced Usage Examples

700

701

### Custom Analysis with Deferred Functions

702

703

```python

704

import ast

705

import pyflakes.checker

706

707

def custom_analysis_callback():

708

"""Custom analysis to run after main traversal."""

709

print("Running custom analysis...")

710

# Access checker state here

711

return

712

713

code = """

714

def func():

715

global_var = 42 # This will be analyzed after global scope

716

717

global_var = 10

718

"""

719

720

tree = ast.parse(code, 'test.py')

721

checker = pyflakes.checker.Checker(tree, 'test.py')

722

723

# Add custom deferred analysis

724

checker.deferFunction(custom_analysis_callback)

725

726

# Analysis completes with our custom function

727

print(f"Found {len(checker.messages)} issues")

728

```

729

730

### Scope Analysis

731

732

```python

733

import ast

734

import pyflakes.checker

735

736

code = """

737

class MyClass:

738

x = 1

739

740

def method(self):

741

y = 2

742

743

def inner():

744

z = 3

745

"""

746

747

tree = ast.parse(code, 'test.py')

748

checker = pyflakes.checker.Checker(tree, 'test.py')

749

750

# Analyze completed scopes

751

print(f"Analyzed {len(checker.deadScopes)} scopes:")

752

for i, scope in enumerate(checker.deadScopes):

753

print(f" Scope {i}: {type(scope).__name__}")

754

print(f" Bindings: {list(scope.keys())}")

755

```

756

757

### Message Analysis by Type

758

759

```python

760

import ast

761

import pyflakes.checker

762

from pyflakes.messages import UnusedImport, UndefinedName

763

764

code = """

765

import os

766

import sys

767

print(undefined_name)

768

unused_var = 42

769

"""

770

771

tree = ast.parse(code, 'test.py')

772

checker = pyflakes.checker.Checker(tree, 'test.py')

773

774

# Categorize messages

775

unused_imports = [m for m in checker.messages if isinstance(m, UnusedImport)]

776

undefined_names = [m for m in checker.messages if isinstance(m, UndefinedName)]

777

778

print(f"Unused imports: {len(unused_imports)}")

779

print(f"Undefined names: {len(undefined_names)}")

780

781

for msg in checker.messages:

782

print(f"{type(msg).__name__}: {msg}")

783

```

784

785

### Annotation System Usage

786

787

The checker provides sophisticated annotation handling that can be extended:

788

789

```python

790

import ast

791

import pyflakes.checker

792

793

code = '''

794

from typing import List, Dict

795

from __future__ import annotations

796

797

def func(x: List[int]) -> Dict[str, int]:

798

"""Function with type annotations."""

799

return {"key": x[0]}

800

801

# Forward reference

802

def forward_ref() -> SomeClass:

803

pass

804

805

class SomeClass:

806

pass

807

'''

808

809

tree = ast.parse(code, 'test.py')

810

checker = pyflakes.checker.Checker(tree, 'test.py')

811

812

# Check if future annotations are enabled

813

print(f"Future annotations enabled: {checker.annotationsFutureEnabled}")

814

815

# All annotations are processed appropriately

816

print(f"Analysis complete with {len(checker.messages)} issues")

817

```