@@ -168,6 +168,21 @@ def _captures(query, root: Node) -> dict[str, list[Node]]:
168168 return cursor .captures (root )
169169
170170
171+ def _matches (query , root : Node ) -> list [tuple [int , dict [str , list [Node ]]]]:
172+ """Return per-match capture groups.
173+
174+ Unlike :func:`_captures` (which groups *all* nodes by capture name into
175+ parallel lists that are **not** guaranteed to be index-aligned across
176+ different capture names), this yields one dict per match so that, e.g.,
177+ a ``@name`` capture is always paired with the ``@def`` capture from the
178+ *same* match. Zipping the two independent lists from ``captures()`` mis-
179+ aligns names and definitions whenever the per-capture node orderings
180+ diverge, scrambling the module symbol table.
181+ """
182+ cursor = QueryCursor (query )
183+ return cursor .matches (root )
184+
185+
171186# ---------------------------------------------------------------------------
172187# Public resolver
173188# ---------------------------------------------------------------------------
@@ -242,46 +257,50 @@ def _index_file(
242257 by_name : dict [str , list [_Definition ]],
243258 ) -> None :
244259 # Top-level functions
245- caps = _captures (self ._queries .top_level_func , root )
246- names = caps .get ("name" , [])
247- defs = caps .get ("def" , [])
248- for name_node , def_node in zip (names , defs ):
249- name = name_node .text .decode ("utf-8" )
250- d = _Definition (mi .file_path , _strip_decorator (def_node ), "func" )
260+ for _ , caps in _matches (self ._queries .top_level_func , root ):
261+ name_nodes = caps .get ("name" , [])
262+ def_nodes = caps .get ("def" , [])
263+ if not name_nodes or not def_nodes :
264+ continue
265+ name = name_nodes [0 ].text .decode ("utf-8" )
266+ d = _Definition (mi .file_path , _strip_decorator (def_nodes [0 ]), "func" )
251267 mi .top_level [name ] = d
252268 by_name [name ].append (d )
253269
254270 # Top-level classes
255- caps = _captures (self ._queries .top_level_class , root )
256- names = caps .get ("name" , [])
257- defs = caps .get ("def" , [])
258- for name_node , def_node in zip (names , defs ):
259- name = name_node .text .decode ("utf-8" )
260- d = _Definition (mi .file_path , _strip_decorator (def_node ), "class" )
271+ for _ , caps in _matches (self ._queries .top_level_class , root ):
272+ name_nodes = caps .get ("name" , [])
273+ def_nodes = caps .get ("def" , [])
274+ if not name_nodes or not def_nodes :
275+ continue
276+ name = name_nodes [0 ].text .decode ("utf-8" )
277+ d = _Definition (mi .file_path , _strip_decorator (def_nodes [0 ]), "class" )
261278 mi .top_level [name ] = d
262279 by_name [name ].append (d )
263280
264281 # Top-level assignments (for class aliases like ``Foo = OtherFoo``)
265- caps = _captures (self ._queries .top_level_assign , root )
266- names = caps .get ("name" , [])
267- defs = caps .get ("def" , [])
268- for name_node , def_node in zip (names , defs ):
269- name = name_node .text .decode ("utf-8" )
282+ for _ , caps in _matches (self ._queries .top_level_assign , root ):
283+ name_nodes = caps .get ("name" , [])
284+ def_nodes = caps .get ("def" , [])
285+ if not name_nodes or not def_nodes :
286+ continue
287+ name = name_nodes [0 ].text .decode ("utf-8" )
270288 if name in mi .top_level :
271289 continue
272- d = _Definition (mi .file_path , def_node , "var" )
290+ d = _Definition (mi .file_path , def_nodes [ 0 ] , "var" )
273291 mi .top_level [name ] = d
274292 by_name [name ].append (d )
275293
276294 # Class methods
277- caps = _captures (self ._queries .class_methods , root )
278- class_names = caps .get ("class_name" , [])
279- method_names = caps .get ("method_name" , [])
280- method_defs = caps .get ("method_def" , [])
281- for cls_node , mname_node , mdef_node in zip (class_names , method_names , method_defs ):
282- class_name = cls_node .text .decode ("utf-8" )
283- method_name = mname_node .text .decode ("utf-8" )
284- d = _Definition (mi .file_path , _strip_decorator (mdef_node ), "method" )
295+ for _ , caps in _matches (self ._queries .class_methods , root ):
296+ class_nodes = caps .get ("class_name" , [])
297+ mname_nodes = caps .get ("method_name" , [])
298+ mdef_nodes = caps .get ("method_def" , [])
299+ if not class_nodes or not mname_nodes or not mdef_nodes :
300+ continue
301+ class_name = class_nodes [0 ].text .decode ("utf-8" )
302+ method_name = mname_nodes [0 ].text .decode ("utf-8" )
303+ d = _Definition (mi .file_path , _strip_decorator (mdef_nodes [0 ]), "method" )
285304 mi .class_methods .setdefault (class_name , {})[method_name ] = d
286305 by_name [method_name ].append (d )
287306
0 commit comments