mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:26:23 -04:00
fix(query): apply a trailing anchor when its optional node is skipped
A trailing `.` after an optional node, e.g. `(p (a)+ @a . (b)? @b .)`, sets `is_last_child` on the `(b)?` step. When `(b)?` matched zero, the zero-skip jumped past that step to completion, so the last-child requirement was never enforced. When a zero-skip would bypass a step carrying `is_last_child`, require that the last matched node really is the last named child. Gated on the `alternative_is_skip` edge marker.
This commit is contained in:
parent
7e7f2584d1
commit
a8486ca239
|
|
@ -1258,6 +1258,45 @@ function foo() {}
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_matches_with_last_child_anchor_after_optional() {
|
||||
allocations::record(|| {
|
||||
let language = get_language("c");
|
||||
let query = Query::new(
|
||||
&language,
|
||||
"(preproc_if (preproc_def)+ @def . (preproc_else)? @else .)",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The optional `(preproc_else)?` is absent, so the trailing anchor's
|
||||
// last-child requirement transfers to the last `preproc_def`. A trailing
|
||||
// comment means the def is not the last child, so nothing matches.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
#if X
|
||||
#define A
|
||||
// c
|
||||
#endif
|
||||
",
|
||||
&[],
|
||||
);
|
||||
|
||||
// With the def as the last child, the (else-less) match is allowed.
|
||||
assert_query_matches(
|
||||
&language,
|
||||
&query,
|
||||
"
|
||||
#if X
|
||||
#define A
|
||||
#endif
|
||||
",
|
||||
&[(0, vec![("def", "#define A\n")])],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_matches_with_last_named_child() {
|
||||
allocations::record(|| {
|
||||
|
|
|
|||
|
|
@ -4450,6 +4450,17 @@ static inline bool ts_query_cursor__advance(
|
|||
k--;
|
||||
}
|
||||
|
||||
// A `?`/`*` zero-skip past a step that carries a trailing last-child
|
||||
// anchor transfers that requirement to the last matched node. The
|
||||
// skip is only valid if that node really is the last named child.
|
||||
if (
|
||||
child_step->alternative_is_skip &&
|
||||
child_step->is_last_child &&
|
||||
has_later_named_siblings
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QueryState *copy = ts_query_cursor__copy_state(self, &child_state);
|
||||
if (copy) {
|
||||
LOG(
|
||||
|
|
|
|||
Loading…
Reference in a new issue