or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-parsing.mderror-handling.mdgrammar-system.mdindex.mdpython-elements.mdtokenization.mdtree-navigation.mdutilities.md

tree-navigation.mddocs/

0

# Tree Navigation

1

2

The syntax tree navigation system provides comprehensive methods for traversing, analyzing, and manipulating parsed Python code. All nodes and leaves inherit from common base classes that provide consistent navigation and introspection APIs.

3

4

## Capabilities

5

6

### Base Tree Classes

7

8

Fundamental classes that form the foundation of parso's syntax tree structure.

9

10

```python { .api }

11

class NodeOrLeaf:

12

"""

13

Base class for all tree nodes and leaves.

14

15

Attributes:

16

parent (BaseNode | None): Parent node, None for root

17

type (str): Node type string matching grammar rules

18

"""

19

20

def get_root_node(self):

21

"""

22

Get the root node of the syntax tree.

23

24

Returns:

25

NodeOrLeaf: Root node (typically Module)

26

"""

27

28

def get_next_sibling(self):

29

"""

30

Get the next sibling node in parent's children.

31

32

Returns:

33

NodeOrLeaf | None: Next sibling or None if last child

34

"""

35

36

def get_previous_sibling(self):

37

"""

38

Get the previous sibling node in parent's children.

39

40

Returns:

41

NodeOrLeaf | None: Previous sibling or None if first child

42

"""

43

44

def get_next_leaf(self):

45

"""

46

Get the next leaf node in tree traversal order.

47

48

Returns:

49

Leaf | None: Next leaf or None if this is the last leaf

50

"""

51

52

def get_previous_leaf(self):

53

"""

54

Get the previous leaf node in tree traversal order.

55

56

Returns:

57

Leaf | None: Previous leaf or None if this is the first leaf

58

"""

59

60

def get_first_leaf(self):

61

"""

62

Get the first leaf node in this subtree.

63

64

Returns:

65

Leaf: First leaf node

66

"""

67

68

def get_last_leaf(self):

69

"""

70

Get the last leaf node in this subtree.

71

72

Returns:

73

Leaf: Last leaf node

74

"""

75

76

def search_ancestor(self, *node_types):

77

"""

78

Search for an ancestor node of specified types.

79

80

Args:

81

*node_types (str): Node type names to search for

82

83

Returns:

84

BaseNode | None: First matching ancestor or None

85

"""

86

87

def get_code(self, include_prefix=True):

88

"""

89

Get the source code for this node/leaf.

90

91

Args:

92

include_prefix (bool): Include whitespace and comments (default: True)

93

94

Returns:

95

str: Source code representation

96

"""

97

98

def dump(self, *, indent=4):

99

"""

100

Get formatted tree dump for debugging.

101

102

Args:

103

indent (int | str | None): Indentation style (default: 4 spaces)

104

105

Returns:

106

str: Formatted tree representation

107

"""

108

109

@property

110

def start_pos(self):

111

"""

112

Starting position of this node/leaf.

113

114

Returns:

115

tuple[int, int]: (line, column) position (1-indexed)

116

"""

117

118

@property

119

def end_pos(self):

120

"""

121

Ending position of this node/leaf.

122

123

Returns:

124

tuple[int, int]: (line, column) position (1-indexed)

125

"""

126

```

127

128

```python { .api }

129

class BaseNode(NodeOrLeaf):

130

"""

131

Base class for nodes with children.

132

133

Attributes:

134

children (list[NodeOrLeaf]): Child nodes and leaves

135

"""

136

137

def get_leaf_for_position(self, position, include_prefixes=False):

138

"""

139

Find the leaf at a specific position.

140

141

Args:

142

position (tuple[int, int]): (line, column) position

143

include_prefixes (bool): Whether to match positions in prefixes

144

145

Returns:

146

Leaf | None: Leaf at position or None

147

148

Raises:

149

ValueError: If position is outside this node's range

150

"""

151

```

152

153

```python { .api }

154

class Leaf(NodeOrLeaf):

155

"""

156

Base class for leaf nodes (tokens).

157

158

Attributes:

159

value (str): Token value/text

160

prefix (str): Preceding whitespace and comments

161

"""

162

163

def get_start_pos_of_prefix(self):

164

"""

165

Get the starting position of this leaf's prefix.

166

167

Returns:

168

tuple[int, int]: (line, column) where prefix starts

169

"""

170

```

171

172

#### Usage Examples

173

174

```python

175

import parso

176

177

# Parse some code

178

module = parso.parse('''

179

def example():

180

# Comment here

181

x = 42

182

return x

183

''')

184

185

# Navigate the tree structure

186

func_def = module.children[0] # Function definition

187

print(f"Function type: {func_def.type}") # 'funcdef'

188

189

# Get position information

190

print(f"Function starts at: {func_def.start_pos}")

191

print(f"Function ends at: {func_def.end_pos}")

192

193

# Navigate to parent and siblings

194

suite = func_def.get_suite() # Function body

195

first_stmt = suite.children[1] # First statement (x = 42)

196

print(f"Next sibling: {first_stmt.get_next_sibling()}")

197

198

# Find leaves (tokens)

199

first_leaf = func_def.get_first_leaf()

200

print(f"First token: '{first_leaf.value}' at {first_leaf.start_pos}")

201

202

last_leaf = func_def.get_last_leaf()

203

print(f"Last token: '{last_leaf.value}' at {last_leaf.start_pos}")

204

```

205

206

### Tree Navigation Patterns

207

208

Common patterns for traversing and analyzing syntax trees.

209

210

#### Sibling Navigation

211

212

```python

213

import parso

214

215

module = parso.parse('''

216

import os

217

import sys

218

from pathlib import Path

219

220

def func1():

221

pass

222

223

def func2():

224

pass

225

''')

226

227

# Navigate through siblings

228

current = module.children[0] # First import

229

while current:

230

print(f"Statement type: {current.type}")

231

current = current.get_next_sibling()

232

233

# Reverse navigation

234

current = module.children[-1] # Last statement

235

while current:

236

print(f"Statement type: {current.type}")

237

current = current.get_previous_sibling()

238

```

239

240

#### Leaf Traversal

241

242

```python

243

import parso

244

245

module = parso.parse('x = 1 + 2')

246

247

# Walk through all tokens

248

leaf = module.get_first_leaf()

249

tokens = []

250

while leaf:

251

if leaf.value.strip(): # Skip empty tokens

252

tokens.append((leaf.value, leaf.start_pos))

253

leaf = leaf.get_next_leaf()

254

255

print("Tokens:", tokens)

256

# Output: [('x', (1, 0)), ('=', (1, 2)), ('1', (1, 4)), ('+', (1, 6)), ('2', (1, 8))]

257

```

258

259

#### Ancestor Search

260

261

```python

262

import parso

263

264

module = parso.parse('''

265

class MyClass:

266

def method(self):

267

if True:

268

x = 42

269

''')

270

271

# Find the assignment statement

272

assignment = None

273

for node in module.get_used_names()['x']:

274

if node.is_definition():

275

assignment = node.parent

276

break

277

278

# Search for containing structures

279

method = assignment.search_ancestor('funcdef')

280

class_def = assignment.search_ancestor('classdef')

281

if_stmt = assignment.search_ancestor('if_stmt')

282

283

print(f"Assignment is in method: {method.name.value}")

284

print(f"Assignment is in class: {class_def.name.value}")

285

print(f"Assignment is in if statement: {if_stmt is not None}")

286

```

287

288

### Position and Code Generation

289

290

Working with position information and regenerating source code.

291

292

```python { .api }

293

def get_start_pos_of_prefix(self):

294

"""Get starting position of prefix (whitespace/comments before token)."""

295

296

def get_code(self, include_prefix=True):

297

"""Get source code, optionally including prefix."""

298

299

@property

300

def start_pos(self):

301

"""Starting (line, column) position."""

302

303

@property

304

def end_pos(self):

305

"""Ending (line, column) position."""

306

```

307

308

#### Usage Examples

309

310

```python

311

import parso

312

313

module = parso.parse('''

314

def func(): # Function comment

315

x = 42 # Variable comment

316

return x

317

''')

318

319

# Position information

320

func_def = module.children[0]

321

print(f"Function definition: {func_def.start_pos} to {func_def.end_pos}")

322

323

# Get code with and without prefixes

324

assignment = func_def.get_suite().children[1] # x = 42

325

print(f"With prefix: {repr(assignment.get_code(include_prefix=True))}")

326

print(f"Without prefix: {repr(assignment.get_code(include_prefix=False))}")

327

328

# Prefix handling for individual tokens

329

x_name = assignment.children[0] # The 'x' token

330

print(f"Token value: '{x_name.value}'")

331

print(f"Token prefix: '{x_name.prefix}'")

332

print(f"Prefix starts at: {x_name.get_start_pos_of_prefix()}")

333

```

334

335

### Position-Based Lookup

336

337

Finding nodes and tokens at specific positions in the source code.

338

339

```python { .api }

340

def get_leaf_for_position(self, position, include_prefixes=False):

341

"""Find leaf at specific position."""

342

```

343

344

#### Usage Examples

345

346

```python

347

import parso

348

349

code = '''def example():

350

result = calculate(1, 2, 3)

351

return result'''

352

353

module = parso.parse(code)

354

355

# Find token at specific positions

356

leaf_at_def = module.get_leaf_for_position((1, 0)) # 'def'

357

leaf_at_name = module.get_leaf_for_position((1, 4)) # 'example'

358

leaf_at_calc = module.get_leaf_for_position((2, 13)) # 'calculate'

359

360

print(f"At (1,0): '{leaf_at_def.value}'")

361

print(f"At (1,4): '{leaf_at_name.value}'")

362

print(f"At (2,13): '{leaf_at_calc.value}'")

363

364

# Handle positions in whitespace/comments

365

code_with_comment = '''def func(): # Comment

366

pass'''

367

368

module = parso.parse(code_with_comment)

369

370

# Position in comment (without include_prefixes)

371

leaf_in_comment = module.get_leaf_for_position((1, 15), include_prefixes=False)

372

print(f"In comment (no prefix): {leaf_in_comment}") # None

373

374

# Position in comment (with include_prefixes)

375

leaf_in_comment = module.get_leaf_for_position((1, 15), include_prefixes=True)

376

print(f"In comment (with prefix): {leaf_in_comment.value}") # 'pass'

377

```

378

379

### Tree Debugging and Inspection

380

381

Tools for debugging and understanding the tree structure.

382

383

```python { .api }

384

def dump(self, *, indent=4):

385

"""Format tree structure for debugging."""

386

```

387

388

#### Usage Examples

389

390

```python

391

import parso

392

393

module = parso.parse('lambda x: x + 1')

394

395

# Pretty-printed tree dump

396

print(module.dump())

397

398

# Compact dump (single line)

399

print(module.dump(indent=None))

400

401

# Custom indentation

402

print(module.dump(indent='\t')) # Tab indentation

403

print(module.dump(indent=2)) # 2-space indentation

404

405

# Dump specific subtrees

406

lambda_node = module.children[0]

407

print("Lambda subtree:")

408

print(lambda_node.dump())

409

```

410

411

### Error Nodes and Recovery

412

413

Working with error nodes when parsing invalid code.

414

415

```python

416

import parso

417

418

# Parse invalid code

419

module = parso.parse('def broken(: pass') # Missing parameter

420

421

# Find error nodes

422

def find_error_nodes(node):

423

"""Recursively find all error nodes in tree."""

424

errors = []

425

if hasattr(node, 'type') and 'error' in node.type:

426

errors.append(node)

427

428

if hasattr(node, 'children'):

429

for child in node.children:

430

errors.extend(find_error_nodes(child))

431

432

return errors

433

434

error_nodes = find_error_nodes(module)

435

for error_node in error_nodes:

436

print(f"Error node: {error_node.type} at {error_node.start_pos}")

437

print(f"Error content: {repr(error_node.get_code())}")

438

```

439

440

### Advanced Navigation Patterns

441

442

Complex navigation patterns for sophisticated code analysis.

443

444

#### Finding All Nodes of a Type

445

446

```python

447

import parso

448

449

def find_all_nodes(root, node_type):

450

"""Find all nodes of a specific type in the tree."""

451

results = []

452

453

def walk(node):

454

if hasattr(node, 'type') and node.type == node_type:

455

results.append(node)

456

if hasattr(node, 'children'):

457

for child in node.children:

458

walk(child)

459

460

walk(root)

461

return results

462

463

module = parso.parse('''

464

def func1():

465

pass

466

467

class MyClass:

468

def method(self):

469

pass

470

471

def func2():

472

pass

473

''')

474

475

# Find all function definitions

476

functions = find_all_nodes(module, 'funcdef')

477

for func in functions:

478

print(f"Function: {func.name.value}")

479

480

# Find all class definitions

481

classes = find_all_nodes(module, 'classdef')

482

for cls in classes:

483

print(f"Class: {cls.name.value}")

484

```

485

486

#### Context-Aware Navigation

487

488

```python

489

import parso

490

491

def get_containing_scope(node):

492

"""Get the containing function, class, or module for a node."""

493

scope_types = ('funcdef', 'classdef', 'file_input')

494

return node.search_ancestor(*scope_types)

495

496

def get_statement_context(node):

497

"""Get contextual information about where a node appears."""

498

# Find the statement containing this node

499

stmt = node

500

while stmt.parent and stmt.parent.type not in ('suite', 'file_input'):

501

stmt = stmt.parent

502

503

# Find containing scope

504

scope = get_containing_scope(node)

505

506

return {

507

'statement': stmt,

508

'scope': scope,

509

'scope_type': scope.type if scope else None,

510

'scope_name': getattr(scope, 'name', None)

511

}

512

513

module = parso.parse('''

514

class Example:

515

def method(self):

516

x = 42

517

return x

518

''')

519

520

# Analyze context for the variable 'x'

521

for name_node in module.get_used_names()['x']:

522

context = get_statement_context(name_node)

523

print(f"Variable 'x' context:")

524

print(f" Statement type: {context['statement'].type}")

525

print(f" Scope type: {context['scope_type']}")

526

if context['scope_name']:

527

print(f" Scope name: {context['scope_name'].value}")

528

```