Fix treesit-query-capture's NODE-ONLY param

Before the fix, if user uses the NODE-ONLY option, we don't keep
the capture names in the results, then predicates won't work
because they can't reference capture names.

* src/treesit.c (query_capture_remove_capture_name): New
function.
(Ftreesit_query_capture): Use the new function to remove capture
names AFTER running the predicate.
* test/src/treesit-tests.el:
(treesit-query-node-only-and-grouped): New test.
This commit is contained in:
Yuan Fu 2026-04-04 22:33:19 -07:00
parent ae1ac739b7
commit 45d7678ca3
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
2 changed files with 71 additions and 17 deletions

View file

@ -3977,6 +3977,22 @@ treesit_initialize_query (Lisp_Object query, const TSLanguage *lang,
}
}
/* Go over a list from START to END (until the element eq to END),
replace (capture-name . node) with just node. */
static void query_capture_remove_capture_name (Lisp_Object start,
Lisp_Object end)
{
Lisp_Object tail = start;
FOR_EACH_TAIL (tail)
{
Lisp_Object cell = CAR (tail);
CHECK_CONS (cell);
XSETCAR (tail, CDR (cell));
if (EQ (CDR (tail), end)) return;
}
}
DEFUN ("treesit-query-capture",
Ftreesit_query_capture,
Streesit_query_capture, 2, 6, 0,
@ -4122,18 +4138,12 @@ the query. */)
TSQueryCapture capture = captures[idx];
Lisp_Object captured_node = make_treesit_node (lisp_parser,
capture.node);
Lisp_Object cap;
if (NILP (node_only))
{
const char *capture_name
= ts_query_capture_name_for_id (treesit_query, capture.index,
&capture_name_len);
cap = Fcons (intern_c_string_1 (capture_name, capture_name_len),
captured_node);
}
else
cap = captured_node;
const char *capture_name
= ts_query_capture_name_for_id (treesit_query, capture.index,
&capture_name_len);
Lisp_Object cap
= Fcons (intern_c_string_1 (capture_name, capture_name_len),
captured_node);
if (NILP (grouped))
result = Fcons (cap, result); /* Mode 1. */
@ -4166,12 +4176,24 @@ the query. */)
if (!NILP (predicate_signal_data))
break;
/* Mode 1: Predicates didn't pass, roll back. */
if (!match && NILP (grouped))
result = prev_result;
/* Mode 2: Predicates pass, add this match group. */
/* Mode 1: Roll back if predicate didn't pass, don't roll back if
predicate passed. */
if (NILP (grouped))
{
if (!match)
result = prev_result;
else if (!NILP (node_only))
query_capture_remove_capture_name (result, prev_result);
}
/* Mode 2: Add this match group if predicate pass, don't add this
group if predicate didn't pass. */
if (match && !NILP (grouped))
result = Fcons (Fnreverse (match_group), result);
{
match_group = Fnreverse (match_group);
if (!NILP (node_only))
query_capture_remove_capture_name (match_group, Qnil);
result = Fcons (match_group, result);
}
}
/* Final clean up. */

View file

@ -590,6 +590,38 @@ BODY is the test body."
(treesit-pattern-expand "a\nb\rc\td\0e\"f\1g\\h\fi")
"\"a\\nb\\rc\\td\\0e\\\"f\1g\\\\h\fi\"")))))
(ert-deftest treesit-query-node-only-and-grouped ()
"Tests for query API."
(skip-unless (treesit-language-available-p 'json))
(with-temp-buffer
(let (parser root-node)
(progn
(insert "[1,2,{\"name\": \"Bob\"},3]")
(setq parser (treesit-parser-create 'json)))
;; Test NODE-ONLY.
(let ((res (treesit-query-capture 'json '((number) @num) nil nil t)))
(should (equal (length res) 3))
;; First element should be a node rather than 'num.
(should (treesit-node-p (nth 0 res))))
;; Test GROUPED.
(let ((res (treesit-query-capture 'json '((number) @num) nil nil nil t)))
(should (equal (length res) 3))
;; First element should be a match group.
(should (consp (nth 0 res)))
;; First element of the match group should be a cons (num . <node>).
(should (consp (nth 0 (nth 0 res))))
(should (eq (car (nth 0 (nth 0 res))) 'num)))
;; Test NODE-ONLY + GROUPED.
(let ((res (treesit-query-capture 'json '((number) @num) nil nil t t)))
(should (equal (length res) 3))
;; First element should be a match group.
(should (consp (nth 0 res)))
;; First element of the match group should be a node.
(should (treesit-node-p (nth 0 (nth 0 res))))))))
;;; Narrow
(ert-deftest treesit-narrow ()