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.
diff --git a/src/treesit.c b/src/treesit.c
index bec37067b50..c08691af3d8 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -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. */
@@ -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 ()