Skip to content

Commit 886b352

Browse files
committed
pkgcheck/bash: sort captures by line and column
It has been observed that the order of nodes returned by tree-sitter's QueryCursor is not necessarily in order wrt line and column in the source, but it appears that some of pkgcheck's usage of tree-sitter assumes that captured nodes are returned in order. There doesn't appear to any features in QueryCursor (or other places) that allows one to specify that returned nodes should be ordered in a specific way, so instead we introduce a decorator on QueryCursor that takes dict of captured nodes and sorts each list of nodes by line and column. Fixes: #702 Signed-off-by: Thomas Bracht Laumann Jespersen <t@laumann.xyz>
1 parent 5e3d093 commit 886b352

1 file changed

Lines changed: 28 additions & 4 deletions

File tree

src/pkgcheck/bash/__init__.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,39 @@
1010
try:
1111
from tree_sitter import QueryCursor
1212

13-
def query(query_str: str) -> "QueryCursor":
13+
def unstable_query(query_str: str) -> "QueryCursor":
1414
return QueryCursor(Query(lang, query_str))
1515
except ImportError: # tree-sitter < 0.25
1616
QueryCursor = Query
17-
query = lang.query
17+
unstable_query = lang.query
1818

1919

2020
parser = Parser(language=lang)
2121

22+
23+
class SortedQueryCursor:
24+
"""
25+
Sort query results by line and column. It's been observed that
26+
query results from tree-sitter are not consistently returned in
27+
the same order, so this class acts as a decorator for QueryCursor
28+
to sort the returned captures.
29+
"""
30+
31+
def __init__(self, query_cursor: QueryCursor):
32+
self._query_cursor = query_cursor
33+
34+
def captures(self, node):
35+
caps = self._query_cursor.captures(node)
36+
return {
37+
key: sorted(nodes, key=lambda n: (n.start_point.row, n.start_point.column))
38+
for key, nodes in caps.items()
39+
}
40+
41+
42+
def query(query_str: str):
43+
return SortedQueryCursor(unstable_query(query_str))
44+
45+
2246
# various parse tree queries
2347
cmd_query = query("(command) @call")
2448
func_query = query("(function_definition) @func")
@@ -39,14 +63,14 @@ def node_str(self, node):
3963
"""Return the ebuild string associated with a given parse tree node."""
4064
return self.data[node.start_byte : node.end_byte].decode("utf8")
4165

42-
def global_query(self, query: QueryCursor):
66+
def global_query(self, query: QueryCursor | SortedQueryCursor):
4367
"""Run a given parse tree query returning only those nodes in global scope."""
4468
for x in self.tree.root_node.children:
4569
# skip nodes in function scope
4670
if x.type != "function_definition":
4771
yield from chain.from_iterable(query.captures(x).values())
4872

49-
def func_query(self, query: QueryCursor):
73+
def func_query(self, query: QueryCursor | SortedQueryCursor):
5074
"""Run a given parse tree query returning only those nodes in function scope."""
5175
for x in self.tree.root_node.children:
5276
# only return nodes in function scope

0 commit comments

Comments
 (0)