codegraph-nav 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. codegraph_nav/__init__.py +194 -0
  2. codegraph_nav/ast_grep_analyzer.py +448 -0
  3. codegraph_nav/cli.py +223 -0
  4. codegraph_nav/code_navigator.py +1328 -0
  5. codegraph_nav/code_search.py +1009 -0
  6. codegraph_nav/colors.py +209 -0
  7. codegraph_nav/completions.py +354 -0
  8. codegraph_nav/dart_analyzer.py +301 -0
  9. codegraph_nav/dependency_graph.py +814 -0
  10. codegraph_nav/domain/__init__.py +20 -0
  11. codegraph_nav/domain/routes.py +337 -0
  12. codegraph_nav/domain/schemas.py +229 -0
  13. codegraph_nav/domain/tags.py +87 -0
  14. codegraph_nav/exporters.py +563 -0
  15. codegraph_nav/go_analyzer.py +273 -0
  16. codegraph_nav/graph/__init__.py +72 -0
  17. codegraph_nav/graph/builder.py +409 -0
  18. codegraph_nav/graph/communities.py +402 -0
  19. codegraph_nav/graph/flows.py +311 -0
  20. codegraph_nav/graph/query.py +380 -0
  21. codegraph_nav/graph/schema.py +266 -0
  22. codegraph_nav/graph/search.py +257 -0
  23. codegraph_nav/graph/store.py +517 -0
  24. codegraph_nav/hints.py +195 -0
  25. codegraph_nav/import_resolver.py +891 -0
  26. codegraph_nav/js_ts_analyzer.py +564 -0
  27. codegraph_nav/line_reader.py +664 -0
  28. codegraph_nav/mcp/__init__.py +39 -0
  29. codegraph_nav/mcp/__main__.py +5 -0
  30. codegraph_nav/mcp/server.py +2228 -0
  31. codegraph_nav/py.typed +2 -0
  32. codegraph_nav/ruby_analyzer.py +259 -0
  33. codegraph_nav/rust_analyzer.py +379 -0
  34. codegraph_nav/token_efficient_renderer.py +743 -0
  35. codegraph_nav/watcher.py +382 -0
  36. codegraph_nav-0.1.0.dist-info/METADATA +487 -0
  37. codegraph_nav-0.1.0.dist-info/RECORD +41 -0
  38. codegraph_nav-0.1.0.dist-info/WHEEL +5 -0
  39. codegraph_nav-0.1.0.dist-info/entry_points.txt +4 -0
  40. codegraph_nav-0.1.0.dist-info/licenses/LICENSE +21 -0
  41. codegraph_nav-0.1.0.dist-info/top_level.txt +1 -0
codegraph_nav/py.typed ADDED
@@ -0,0 +1,2 @@
1
+ # Marker file for PEP 561
2
+ # This package supports type checking
@@ -0,0 +1,259 @@
1
+ """Ruby analyzer using tree-sitter for AST-based symbol extraction.
2
+
3
+ Falls back to regex-based GenericAnalyzer when tree-sitter is not installed.
4
+
5
+ Example:
6
+ >>> from codegraph_nav.ruby_analyzer import RubyAnalyzer
7
+ >>> source = '''
8
+ ... def greet(name)
9
+ ... "Hello, #{name}!"
10
+ ... end
11
+ ... '''
12
+ >>> analyzer = RubyAnalyzer('example.rb', source)
13
+ >>> symbols = analyzer.analyze()
14
+ >>> print(symbols[0].name)
15
+ 'greet'
16
+
17
+ Installation:
18
+ To enable AST support, install with the 'ast' extra:
19
+ pip install codegraph-nav[ast]
20
+ """
21
+
22
+ import sys
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from tree_sitter import Node
27
+
28
+ try:
29
+ import tree_sitter_ruby as ts_ruby
30
+ from tree_sitter import Language, Parser
31
+
32
+ TREE_SITTER_AVAILABLE = True
33
+ except ImportError:
34
+ TREE_SITTER_AVAILABLE = False
35
+
36
+ from .code_navigator import GenericAnalyzer, Symbol
37
+
38
+
39
+ class RubyAnalyzer:
40
+ """Analyzes Ruby files using tree-sitter for accurate symbol extraction.
41
+
42
+ When tree-sitter is not available, automatically falls back to regex-based
43
+ GenericAnalyzer.
44
+
45
+ Attributes:
46
+ file_path: Path to the file being analyzed.
47
+ source: Source code content.
48
+ symbols: Extracted symbols.
49
+
50
+ Example:
51
+ >>> source = '''
52
+ ... class User
53
+ ... def initialize(name)
54
+ ... @name = name
55
+ ... end
56
+ ...
57
+ ... def greet
58
+ ... "Hello, #{@name}!"
59
+ ... end
60
+ ... end
61
+ ... '''
62
+ >>> analyzer = RubyAnalyzer('user.rb', source)
63
+ >>> symbols = analyzer.analyze()
64
+ >>> print([s.name for s in symbols])
65
+ ['User', 'initialize', 'greet']
66
+ """
67
+
68
+ def __init__(self, file_path: str, source: str):
69
+ self.file_path = file_path
70
+ self.source = source
71
+ self.lines = source.split("\n")
72
+ self.symbols: list[Symbol] = []
73
+ self._current_class: str | None = None
74
+ self._current_module: str | None = None
75
+
76
+ def analyze(self) -> list[Symbol]:
77
+ """Parse and analyze the file.
78
+
79
+ Returns:
80
+ List of Symbol objects found in the file.
81
+ """
82
+ if not TREE_SITTER_AVAILABLE:
83
+ fallback = GenericAnalyzer(self.file_path, self.source, "ruby")
84
+ return fallback.analyze()
85
+
86
+ try:
87
+ parser = Parser(Language(ts_ruby.language()))
88
+ tree = parser.parse(bytes(self.source, "utf-8"))
89
+ self._visit_node(tree.root_node)
90
+ except Exception as e:
91
+ print(f"tree-sitter error in {self.file_path}: {e}", file=sys.stderr)
92
+ fallback = GenericAnalyzer(self.file_path, self.source, "ruby")
93
+ return fallback.analyze()
94
+
95
+ return self.symbols
96
+
97
+ def _visit_node(self, node: "Node") -> None:
98
+ """Recursively visit AST nodes and extract symbols."""
99
+ node_type = node.type
100
+
101
+ if node_type == "method":
102
+ self._extract_method(node)
103
+ return # Don't visit children (nested defs handled separately)
104
+ elif node_type == "singleton_method":
105
+ self._extract_singleton_method(node)
106
+ return
107
+ elif node_type == "class":
108
+ self._extract_class(node)
109
+ return # Class body visited inside _extract_class
110
+ elif node_type == "module":
111
+ self._extract_module(node)
112
+ return
113
+
114
+ for child in node.children:
115
+ self._visit_node(child)
116
+
117
+ def _get_node_text(self, node: "Node") -> str:
118
+ return self.source[node.start_byte : node.end_byte]
119
+
120
+ def _get_child_by_type(self, node: "Node", type_name: str) -> "Node | None":
121
+ for child in node.children:
122
+ if child.type == type_name:
123
+ return child
124
+ return None
125
+
126
+ def _extract_method(self, node: "Node") -> None:
127
+ """Extract a method definition."""
128
+ name_node = self._get_child_by_type(node, "identifier")
129
+ if not name_node:
130
+ return
131
+
132
+ name = self._get_node_text(name_node)
133
+
134
+ # Get parameters
135
+ params = ""
136
+ params_node = self._get_child_by_type(node, "method_parameters")
137
+ if params_node:
138
+ params = self._get_node_text(params_node)
139
+
140
+ # Determine type based on context
141
+ parent = self._current_class or self._current_module
142
+ symbol_type = "method" if parent else "function"
143
+
144
+ signature = f"def {name}{params}"
145
+
146
+ self.symbols.append(
147
+ Symbol(
148
+ name=name,
149
+ type=symbol_type,
150
+ file_path=self.file_path,
151
+ line_start=node.start_point[0] + 1,
152
+ line_end=node.end_point[0] + 1,
153
+ signature=signature[:100],
154
+ parent=parent,
155
+ )
156
+ )
157
+
158
+ def _extract_singleton_method(self, node: "Node") -> None:
159
+ """Extract a singleton (class-level) method like def self.method_name."""
160
+ # Get the method name
161
+ name_node = self._get_child_by_type(node, "identifier")
162
+ if not name_node:
163
+ return
164
+
165
+ name = self._get_node_text(name_node)
166
+
167
+ # Get parameters
168
+ params = ""
169
+ params_node = self._get_child_by_type(node, "method_parameters")
170
+ if params_node:
171
+ params = self._get_node_text(params_node)
172
+
173
+ parent = self._current_class or self._current_module
174
+ signature = f"def self.{name}{params}"
175
+
176
+ self.symbols.append(
177
+ Symbol(
178
+ name=name,
179
+ type="method",
180
+ file_path=self.file_path,
181
+ line_start=node.start_point[0] + 1,
182
+ line_end=node.end_point[0] + 1,
183
+ signature=signature[:100],
184
+ parent=parent,
185
+ )
186
+ )
187
+
188
+ def _extract_class(self, node: "Node") -> None:
189
+ """Extract a class declaration and visit its body."""
190
+ # Get class name (constant node)
191
+ name_node = self._get_child_by_type(node, "constant")
192
+ if not name_node:
193
+ # Try scope_resolution for nested classes like A::B
194
+ name_node = self._get_child_by_type(node, "scope_resolution")
195
+ if not name_node:
196
+ return
197
+
198
+ name = self._get_node_text(name_node)
199
+
200
+ # Get superclass
201
+ superclass = ""
202
+ sup_node = self._get_child_by_type(node, "superclass")
203
+ if sup_node:
204
+ for child in sup_node.children:
205
+ if child.type in ("constant", "scope_resolution"):
206
+ superclass = f" < {self._get_node_text(child)}"
207
+ break
208
+
209
+ signature = f"class {name}{superclass}"
210
+
211
+ self.symbols.append(
212
+ Symbol(
213
+ name=name,
214
+ type="class",
215
+ file_path=self.file_path,
216
+ line_start=node.start_point[0] + 1,
217
+ line_end=node.end_point[0] + 1,
218
+ signature=signature[:100],
219
+ )
220
+ )
221
+
222
+ # Visit body with class context
223
+ old_class = self._current_class
224
+ self._current_class = name
225
+ body = self._get_child_by_type(node, "body_statement")
226
+ if body:
227
+ for child in body.children:
228
+ self._visit_node(child)
229
+ self._current_class = old_class
230
+
231
+ def _extract_module(self, node: "Node") -> None:
232
+ """Extract a module declaration and visit its body."""
233
+ name_node = self._get_child_by_type(node, "constant")
234
+ if not name_node:
235
+ return
236
+
237
+ name = self._get_node_text(name_node)
238
+
239
+ signature = f"module {name}"
240
+
241
+ self.symbols.append(
242
+ Symbol(
243
+ name=name,
244
+ type="module",
245
+ file_path=self.file_path,
246
+ line_start=node.start_point[0] + 1,
247
+ line_end=node.end_point[0] + 1,
248
+ signature=signature[:100],
249
+ )
250
+ )
251
+
252
+ # Visit body with module context
253
+ old_module = self._current_module
254
+ self._current_module = name
255
+ body = self._get_child_by_type(node, "body_statement")
256
+ if body:
257
+ for child in body.children:
258
+ self._visit_node(child)
259
+ self._current_module = old_module
@@ -0,0 +1,379 @@
1
+ """Rust analyzer using tree-sitter for AST-based symbol extraction.
2
+
3
+ Falls back to regex-based GenericAnalyzer when tree-sitter is not installed.
4
+
5
+ Example:
6
+ >>> from codegraph_nav.rust_analyzer import RustAnalyzer
7
+ >>> source = '''
8
+ ... pub fn greet(name: &str) -> String {
9
+ ... format!("Hello, {}!", name)
10
+ ... }
11
+ ... '''
12
+ >>> analyzer = RustAnalyzer('example.rs', source)
13
+ >>> symbols = analyzer.analyze()
14
+ >>> print(symbols[0].name)
15
+ 'greet'
16
+
17
+ Installation:
18
+ To enable AST support, install with the 'ast' extra:
19
+ pip install codegraph-nav[ast]
20
+ """
21
+
22
+ import sys
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from tree_sitter import Node
27
+
28
+ try:
29
+ import tree_sitter_rust as ts_rust
30
+ from tree_sitter import Language, Parser
31
+
32
+ TREE_SITTER_AVAILABLE = True
33
+ except ImportError:
34
+ TREE_SITTER_AVAILABLE = False
35
+
36
+ from .code_navigator import GenericAnalyzer, Symbol
37
+
38
+
39
+ class RustAnalyzer:
40
+ """Analyzes Rust files using tree-sitter for accurate symbol extraction.
41
+
42
+ When tree-sitter is not available, automatically falls back to regex-based
43
+ GenericAnalyzer.
44
+
45
+ Attributes:
46
+ file_path: Path to the file being analyzed.
47
+ source: Source code content.
48
+ symbols: Extracted symbols.
49
+
50
+ Example:
51
+ >>> source = '''
52
+ ... pub struct User {
53
+ ... name: String,
54
+ ... }
55
+ ...
56
+ ... impl User {
57
+ ... pub fn new(name: String) -> Self {
58
+ ... User { name }
59
+ ... }
60
+ ... }
61
+ ... '''
62
+ >>> analyzer = RustAnalyzer('user.rs', source)
63
+ >>> symbols = analyzer.analyze()
64
+ >>> print([s.name for s in symbols])
65
+ ['User', 'User', 'new']
66
+ """
67
+
68
+ def __init__(self, file_path: str, source: str):
69
+ self.file_path = file_path
70
+ self.source = source
71
+ self.lines = source.split("\n")
72
+ self.symbols: list[Symbol] = []
73
+ self._current_impl: str | None = None
74
+ self._current_trait_impl: str | None = None
75
+
76
+ def analyze(self) -> list[Symbol]:
77
+ """Parse and analyze the file.
78
+
79
+ Returns:
80
+ List of Symbol objects found in the file.
81
+ """
82
+ if not TREE_SITTER_AVAILABLE:
83
+ fallback = GenericAnalyzer(self.file_path, self.source, "rust")
84
+ return fallback.analyze()
85
+
86
+ try:
87
+ parser = Parser(Language(ts_rust.language()))
88
+ tree = parser.parse(bytes(self.source, "utf-8"))
89
+ self._visit_node(tree.root_node)
90
+ except Exception as e:
91
+ print(f"tree-sitter error in {self.file_path}: {e}", file=sys.stderr)
92
+ fallback = GenericAnalyzer(self.file_path, self.source, "rust")
93
+ return fallback.analyze()
94
+
95
+ return self.symbols
96
+
97
+ def _visit_node(self, node: "Node") -> None:
98
+ """Recursively visit AST nodes and extract symbols."""
99
+ node_type = node.type
100
+
101
+ if node_type == "function_item":
102
+ self._extract_function(node)
103
+ elif node_type == "struct_item":
104
+ self._extract_struct(node)
105
+ elif node_type == "enum_item":
106
+ self._extract_enum(node)
107
+ elif node_type == "trait_item":
108
+ self._extract_trait(node)
109
+ elif node_type == "impl_item":
110
+ self._extract_impl(node)
111
+ return # Body visited inside _extract_impl
112
+ elif node_type == "type_item":
113
+ self._extract_type_alias(node)
114
+ elif node_type == "const_item":
115
+ self._extract_const(node)
116
+ elif node_type == "mod_item":
117
+ self._extract_mod(node)
118
+ return # Body visited inside _extract_mod
119
+
120
+ for child in node.children:
121
+ self._visit_node(child)
122
+
123
+ def _get_node_text(self, node: "Node") -> str:
124
+ return self.source[node.start_byte : node.end_byte]
125
+
126
+ def _get_child_by_type(self, node: "Node", type_name: str) -> "Node | None":
127
+ for child in node.children:
128
+ if child.type == type_name:
129
+ return child
130
+ return None
131
+
132
+ def _has_visibility(self, node: "Node") -> bool:
133
+ return self._get_child_by_type(node, "visibility_modifier") is not None
134
+
135
+ def _is_async(self, node: "Node") -> bool:
136
+ mods = self._get_child_by_type(node, "function_modifiers")
137
+ if mods:
138
+ for child in mods.children:
139
+ if child.type == "async":
140
+ return True
141
+ return False
142
+
143
+ def _get_type_params(self, node: "Node") -> str:
144
+ tp = self._get_child_by_type(node, "type_parameters")
145
+ if tp:
146
+ return self._get_node_text(tp)
147
+ return ""
148
+
149
+ def _extract_function(self, node: "Node") -> None:
150
+ """Extract a function/method item."""
151
+ name_node = self._get_child_by_type(node, "identifier")
152
+ if not name_node:
153
+ return
154
+
155
+ name = self._get_node_text(name_node)
156
+
157
+ # Visibility
158
+ pub = "pub " if self._has_visibility(node) else ""
159
+ async_prefix = "async " if self._is_async(node) else ""
160
+ type_params = self._get_type_params(node)
161
+
162
+ # Parameters
163
+ params = ""
164
+ params_node = self._get_child_by_type(node, "parameters")
165
+ if params_node:
166
+ params = self._get_node_text(params_node)
167
+
168
+ # Return type
169
+ ret = ""
170
+ for i, child in enumerate(node.children):
171
+ if child.type == "->":
172
+ remaining = [c for c in node.children[i + 1 :] if c.type != "block"]
173
+ if remaining:
174
+ ret = " -> " + self._get_node_text(remaining[0])
175
+ break
176
+
177
+ # Determine type
178
+ parent = self._current_impl
179
+ symbol_type = "method" if parent else "function"
180
+
181
+ signature = f"{pub}{async_prefix}fn {name}{type_params}{params}{ret}"
182
+
183
+ self.symbols.append(
184
+ Symbol(
185
+ name=name,
186
+ type=symbol_type,
187
+ file_path=self.file_path,
188
+ line_start=node.start_point[0] + 1,
189
+ line_end=node.end_point[0] + 1,
190
+ signature=signature[:100],
191
+ parent=parent,
192
+ )
193
+ )
194
+
195
+ def _extract_struct(self, node: "Node") -> None:
196
+ """Extract a struct definition."""
197
+ name_node = self._get_child_by_type(node, "type_identifier")
198
+ if not name_node:
199
+ return
200
+
201
+ name = self._get_node_text(name_node)
202
+ pub = "pub " if self._has_visibility(node) else ""
203
+ type_params = self._get_type_params(node)
204
+ signature = f"{pub}struct {name}{type_params}"
205
+
206
+ self.symbols.append(
207
+ Symbol(
208
+ name=name,
209
+ type="struct",
210
+ file_path=self.file_path,
211
+ line_start=node.start_point[0] + 1,
212
+ line_end=node.end_point[0] + 1,
213
+ signature=signature[:100],
214
+ )
215
+ )
216
+
217
+ def _extract_enum(self, node: "Node") -> None:
218
+ """Extract an enum definition."""
219
+ name_node = self._get_child_by_type(node, "type_identifier")
220
+ if not name_node:
221
+ return
222
+
223
+ name = self._get_node_text(name_node)
224
+ pub = "pub " if self._has_visibility(node) else ""
225
+ signature = f"{pub}enum {name}"
226
+
227
+ self.symbols.append(
228
+ Symbol(
229
+ name=name,
230
+ type="enum",
231
+ file_path=self.file_path,
232
+ line_start=node.start_point[0] + 1,
233
+ line_end=node.end_point[0] + 1,
234
+ signature=signature[:100],
235
+ )
236
+ )
237
+
238
+ def _extract_trait(self, node: "Node") -> None:
239
+ """Extract a trait definition."""
240
+ name_node = self._get_child_by_type(node, "type_identifier")
241
+ if not name_node:
242
+ return
243
+
244
+ name = self._get_node_text(name_node)
245
+ pub = "pub " if self._has_visibility(node) else ""
246
+ type_params = self._get_type_params(node)
247
+ signature = f"{pub}trait {name}{type_params}"
248
+
249
+ self.symbols.append(
250
+ Symbol(
251
+ name=name,
252
+ type="trait",
253
+ file_path=self.file_path,
254
+ line_start=node.start_point[0] + 1,
255
+ line_end=node.end_point[0] + 1,
256
+ signature=signature[:100],
257
+ )
258
+ )
259
+
260
+ def _extract_impl(self, node: "Node") -> None:
261
+ """Extract an impl block and visit its methods."""
262
+ # Get the type being implemented
263
+ # For "impl Type", get Type
264
+ # For "impl Trait for Type", get Type (the target)
265
+ impl_type = None
266
+ trait_name = None
267
+
268
+ type_ids = [c for c in node.children if c.type == "type_identifier"]
269
+ scoped_ids = [c for c in node.children if c.type == "scoped_type_identifier"]
270
+ has_for = any(c.type == "for" for c in node.children)
271
+
272
+ if has_for:
273
+ # impl Trait for Type
274
+ if len(type_ids) >= 1:
275
+ impl_type = self._get_node_text(type_ids[-1])
276
+ if scoped_ids:
277
+ trait_name = self._get_node_text(scoped_ids[0])
278
+ elif len(type_ids) >= 2:
279
+ trait_name = self._get_node_text(type_ids[0])
280
+ else:
281
+ # impl Type
282
+ if type_ids:
283
+ impl_type = self._get_node_text(type_ids[0])
284
+
285
+ if impl_type:
286
+ type_params = self._get_type_params(node)
287
+ if trait_name:
288
+ sig = f"impl {trait_name} for {impl_type}{type_params}"
289
+ else:
290
+ sig = f"impl {impl_type}{type_params}"
291
+
292
+ self.symbols.append(
293
+ Symbol(
294
+ name=impl_type,
295
+ type="impl",
296
+ file_path=self.file_path,
297
+ line_start=node.start_point[0] + 1,
298
+ line_end=node.end_point[0] + 1,
299
+ signature=sig[:100],
300
+ )
301
+ )
302
+
303
+ # Visit body with impl context
304
+ old_impl = self._current_impl
305
+ self._current_impl = impl_type
306
+ decl_list = self._get_child_by_type(node, "declaration_list")
307
+ if decl_list:
308
+ for child in decl_list.children:
309
+ self._visit_node(child)
310
+ self._current_impl = old_impl
311
+
312
+ def _extract_type_alias(self, node: "Node") -> None:
313
+ """Extract a type alias."""
314
+ name_node = self._get_child_by_type(node, "type_identifier")
315
+ if not name_node:
316
+ return
317
+
318
+ name = self._get_node_text(name_node)
319
+ pub = "pub " if self._has_visibility(node) else ""
320
+ type_params = self._get_type_params(node)
321
+ signature = f"{pub}type {name}{type_params}"
322
+
323
+ self.symbols.append(
324
+ Symbol(
325
+ name=name,
326
+ type="type",
327
+ file_path=self.file_path,
328
+ line_start=node.start_point[0] + 1,
329
+ line_end=node.end_point[0] + 1,
330
+ signature=signature[:100],
331
+ )
332
+ )
333
+
334
+ def _extract_const(self, node: "Node") -> None:
335
+ """Extract a const item."""
336
+ name_node = self._get_child_by_type(node, "identifier")
337
+ if not name_node:
338
+ return
339
+
340
+ name = self._get_node_text(name_node)
341
+ pub = "pub " if self._has_visibility(node) else ""
342
+ signature = f"{pub}const {name}"
343
+
344
+ self.symbols.append(
345
+ Symbol(
346
+ name=name,
347
+ type="const",
348
+ file_path=self.file_path,
349
+ line_start=node.start_point[0] + 1,
350
+ line_end=node.end_point[0] + 1,
351
+ signature=signature[:100],
352
+ )
353
+ )
354
+
355
+ def _extract_mod(self, node: "Node") -> None:
356
+ """Extract a module declaration and visit its body."""
357
+ name_node = self._get_child_by_type(node, "identifier")
358
+ if not name_node:
359
+ return
360
+
361
+ name = self._get_node_text(name_node)
362
+ signature = f"mod {name}"
363
+
364
+ self.symbols.append(
365
+ Symbol(
366
+ name=name,
367
+ type="module",
368
+ file_path=self.file_path,
369
+ line_start=node.start_point[0] + 1,
370
+ line_end=node.end_point[0] + 1,
371
+ signature=signature[:100],
372
+ )
373
+ )
374
+
375
+ # Visit body
376
+ decl_list = self._get_child_by_type(node, "declaration_list")
377
+ if decl_list:
378
+ for child in decl_list.children:
379
+ self._visit_node(child)