mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
fix(query): keep the trailing anchor when a zero-matched quantifier is anchored
on both sides
In `A . Q* . B`, a zero-matched `Q` made both anchors vacuous, so `A` and `B`
were no longer required to be immediate siblings. Only relax the following
anchor on a `?`/`*` zero-skip when the skipped step has no leading anchor of
its own; otherwise the adjacency transfers through the empty run.
(cherry picked from commit dfcf73921c)
This commit is contained in:
parent
b6243a5234
commit
fcb38e9dcf
|
|
@ -1296,6 +1296,46 @@ fn test_query_matches_with_last_child_anchor_after_optional() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_matches_with_anchors_on_both_sides_of_zero_quantifier() {
|
||||
allocations::record(|| {
|
||||
let language = get_language("javascript");
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"(program (lexical_declaration) @a . (comment)* . (function_declaration) @b)",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Anchors on both sides of a zero-matched quantifier collapse into a single
|
||||
// adjacency constraint: with no comments, the declaration must be immediately
|
||||
// followed by the function.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
const a = 1;
|
||||
const b = 2;
|
||||
function foo() {}
|
||||
",
|
||||
&[(0, vec![("a", "const b = 2;"), ("b", "function foo() {}")])],
|
||||
);
|
||||
|
||||
// With a comment present the quantifier is non-zero, so the anchors apply
|
||||
// normally: the comment must sit immediately between the declaration and the
|
||||
// function.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
const b = 2;
|
||||
// c
|
||||
function foo() {}
|
||||
",
|
||||
&[(0, vec![("a", "const b = 2;"), ("b", "function foo() {}")])],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_matches_with_last_named_child() {
|
||||
allocations::record(|| {
|
||||
|
|
|
|||
|
|
@ -239,6 +239,21 @@ nearest node that the pattern _does_ match. For example, given
|
|||
the trailing anchor requires the last matched node to be the parent's last named child: when a
|
||||
`preproc_else` is present it must be last. When it is absent, the last `preproc_def` must be last.
|
||||
|
||||
Similarly, if an optionally quantified node is anchored between two siblings and matches zero nodes,
|
||||
both sibling anchors collapse into one, constraining the outer nodes together. For example, given
|
||||
|
||||
```query
|
||||
(translation_unit
|
||||
(declaration) @a
|
||||
.
|
||||
(comment)*
|
||||
.
|
||||
(function_definition) @b)
|
||||
```
|
||||
|
||||
If there are no comments, `(declaration)` and `(function_definition)` must be immediate siblings
|
||||
in order for the query to match.
|
||||
|
||||
An anchor may not appear at the first or last position inside a group `(...)` or an alternation
|
||||
`[...]`. A group or alternation is not a node, so it has no first or last child to anchor against,
|
||||
and there is no sibling on that side to anchor to. For example, write `(comment)* @doc . (function)`
|
||||
|
|
|
|||
|
|
@ -4445,8 +4445,12 @@ static inline bool ts_query_cursor__advance(
|
|||
copy->seeking_immediate_match = true;
|
||||
}
|
||||
// Taking a `?`/`*` zero-skip means the quantified subpattern matched
|
||||
// nothing, so an immediately-following anchor is vacuous for this copy.
|
||||
if (child_step->alternative_is_skip) {
|
||||
// nothing, so an immediately-following anchor is vacuous for this copy,
|
||||
// UNLESS the skipped step carried a leading anchor of its own. In that
|
||||
// case the adjacency transfers through the empty run to the step we skip
|
||||
// to, so `A . Q* . B` with zero `Q` still requires `A` and `B` to be
|
||||
// immediate siblings.
|
||||
if (child_step->alternative_is_skip && !child_step->is_immediate) {
|
||||
copy->skipped_quantifier = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue