fix(query): forbid an anchor at the end of a group

A `.` at the end of a group, such as `((comment)+ @c .)`, was silently dropped
during compilation. Reject rather than dropping.
This commit is contained in:
Will Lillis 2026-06-28 20:31:24 -04:00
parent 6bfbd9d84d
commit 966dc52557
No known key found for this signature in database
2 changed files with 53 additions and 0 deletions

View file

@ -256,6 +256,51 @@ fn test_query_errors_on_invalid_syntax() {
});
}
#[test]
fn test_query_errors_on_anchor_at_group_edge() {
allocations::record(|| {
let language = get_language("javascript");
// Anchors between siblings, or at the first/last position of a *node*
// pattern, are valid.
assert!(Query::new(&language, "((_) . (_))").is_ok());
assert!(Query::new(&language, "(program (_) (_) .)").is_ok());
assert!(Query::new(&language, "(program (_)* @x . (_))").is_ok());
// A `.` at the edge of a *group* is rejected. A group is not a node, so it
// has no last child to anchor, and there is no sibling within the group to
// anchor to.
assert_eq!(
Query::new(&language, "((_) .)").unwrap_err(),
QueryError {
row: 0,
offset: 5,
column: 5,
kind: QueryErrorKind::Syntax,
message: [
"((_) .)", //
" ^"
]
.join("\n")
}
);
assert_eq!(
Query::new(&language, "(program ((_)+ .)? (_))").unwrap_err(),
QueryError {
row: 0,
offset: 15,
column: 15,
kind: QueryErrorKind::Syntax,
message: [
"(program ((_)+ .)? (_))", //
" ^"
]
.join("\n")
}
);
});
}
#[test]
fn test_query_errors_on_invalid_symbols() {
allocations::record(|| {

View file

@ -2485,9 +2485,17 @@ static TSQueryError ts_query__parse_pattern(
CaptureQuantifiers child_capture_quantifiers = capture_quantifiers_new();
for (;;) {
if (stream->next == '.') {
const char *anchor_start = stream->input;
child_is_immediate = true;
stream_advance(stream);
stream_skip_whitespace(stream);
// A `.` at a group's end has no sibling to anchor, and a group is not a
// node, so there is no last child to anchor against.
if (stream->next == ')') {
stream_reset(stream, anchor_start);
capture_quantifiers_delete(&child_capture_quantifiers);
return TSQueryErrorSyntax;
}
}
TSQueryError e = ts_query__parse_pattern(
self,