mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:26:23 -04:00
fix(lib): continue search for later named siblings in
`ts_tree_cursor_current_status` By terminating early on `has_later_siblings`, `has_later_named_siblings` was incorrectly reported as `false` in some cases. This led to the execution of some queries to terminate early. Also remove some dead branches inside `ts_tree_cursor_current_status`. Co-authored-by: Will Lillis <will.lillis24@gmail.com>
This commit is contained in:
parent
003b10c280
commit
308aee0c90
|
|
@ -6485,3 +6485,64 @@ export default grammar({
|
|||
|
||||
assert_query_matches(&language, &query, source, &[(0, vec![("tuple", "()")])]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_last_child_anchor_looks_past_hidden_repeat() {
|
||||
let language = get_test_fixture_language("last_child_anchor_past_hidden_repeat");
|
||||
|
||||
let source = "T a.b.c\nL a.b.c\nN a!.b!.c\n";
|
||||
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"
|
||||
(trailing_sep (name) @last .)
|
||||
(leading_sep (name) @last .)
|
||||
(trailing_named (name) @last .)
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
source,
|
||||
&[
|
||||
(0, vec![("last", "c")]),
|
||||
(1, vec![("last", "c")]),
|
||||
(2, vec![("last", "c")]),
|
||||
],
|
||||
);
|
||||
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"
|
||||
(trailing_sep . (name) @first)
|
||||
(trailing_sep (name) @a . (name) @b)
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
source,
|
||||
&[
|
||||
(0, vec![("first", "a")]),
|
||||
(1, vec![("a", "a"), ("b", "b")]),
|
||||
(1, vec![("a", "b"), ("b", "c")]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_last_child_anchor_looks_past_hidden_node() {
|
||||
allocations::record(|| {
|
||||
let language = get_language("c");
|
||||
|
||||
let query = Query::new(&language, "(translation_unit (_) @last .)").unwrap();
|
||||
|
||||
let source = "enum E { A };\nint x;\nint y;\n";
|
||||
|
||||
assert_query_matches(&language, &query, source, &[(0, vec![("last", "int y;")])]);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -557,8 +557,10 @@ void ts_tree_cursor_current_status(
|
|||
(*supertype_count)++;
|
||||
}
|
||||
|
||||
// Determine if the current node has later siblings.
|
||||
if (!*has_later_siblings) {
|
||||
// Determine if the current node has later siblings. A later *anonymous*
|
||||
// sibling settles `has_later_siblings` but says nothing about later *named*
|
||||
// siblings.
|
||||
if (!*has_later_named_siblings) {
|
||||
unsigned sibling_count = parent_entry->subtree->ptr->child_count;
|
||||
unsigned structural_child_index = entry->structural_child_index;
|
||||
if (!ts_subtree_extra(*entry->subtree)) structural_child_index++;
|
||||
|
|
@ -570,14 +572,12 @@ void ts_tree_cursor_current_status(
|
|||
);
|
||||
if (sibling_metadata.visible) {
|
||||
*has_later_siblings = true;
|
||||
if (*has_later_named_siblings) break;
|
||||
if (sibling_metadata.named) {
|
||||
*has_later_named_siblings = true;
|
||||
break;
|
||||
}
|
||||
} else if (ts_subtree_visible_child_count(sibling) > 0) {
|
||||
*has_later_siblings = true;
|
||||
if (*has_later_named_siblings) break;
|
||||
if (sibling.ptr->named_child_count > 0) {
|
||||
*has_later_named_siblings = true;
|
||||
break;
|
||||
|
|
|
|||
43
test/fixtures/test_grammars/last_child_anchor_past_hidden_repeat/corpus.txt
vendored
Normal file
43
test/fixtures/test_grammars/last_child_anchor_past_hidden_repeat/corpus.txt
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
=========================================
|
||||
Repeat ending with an anonymous separator
|
||||
=========================================
|
||||
|
||||
T a.b.c
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(trailing_sep
|
||||
(name)
|
||||
(name)
|
||||
(name)))
|
||||
|
||||
===============================
|
||||
Repeat ending with a named node
|
||||
===============================
|
||||
|
||||
L a.b.c
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(leading_sep
|
||||
(name)
|
||||
(name)
|
||||
(name)))
|
||||
|
||||
============================================================
|
||||
Repeat ending with an anonymous separator, preceded by a tag
|
||||
============================================================
|
||||
|
||||
N a!.b!.c
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(trailing_named
|
||||
(name)
|
||||
(tag)
|
||||
(name)
|
||||
(tag)
|
||||
(name)))
|
||||
25
test/fixtures/test_grammars/last_child_anchor_past_hidden_repeat/grammar.js
vendored
Normal file
25
test/fixtures/test_grammars/last_child_anchor_past_hidden_repeat/grammar.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// These three rules all parse to a flat run of `name` children, but `repeat`
|
||||
// builds a different hidden `_repeat1` node for each of them, so a query's
|
||||
// trailing `.` anchor has to walk past a different set of hidden nodes to
|
||||
// decide whether a `name` is the last *named* child of its rule.
|
||||
export default grammar({
|
||||
name: 'last_child_anchor_past_hidden_repeat',
|
||||
|
||||
rules: {
|
||||
source_file: $ => repeat(choice($.trailing_sep, $.leading_sep, $.trailing_named)),
|
||||
|
||||
// The hidden repeat ends with an anonymous node, and a named node follows it.
|
||||
trailing_sep: $ => seq('T', repeat(seq($.name, '.')), $.name),
|
||||
|
||||
// The hidden repeat ends with a named node.
|
||||
leading_sep: $ => seq('L', $.name, repeat(seq('.', $.name))),
|
||||
|
||||
// Same shape as `trailing_sep`, but a named node sits inside the repeat
|
||||
// ahead of the anonymous separator.
|
||||
trailing_named: $ => seq('N', repeat(seq($.name, $.tag, '.')), $.name),
|
||||
|
||||
tag: _ => '!',
|
||||
|
||||
name: _ => /[a-z]+/,
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue