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
```