diff --git a/crates/cli/src/tests/query_test.rs b/crates/cli/src/tests/query_test.rs index c8160975f..1980a867c 100644 --- a/crates/cli/src/tests/query_test.rs +++ b/crates/cli/src/tests/query_test.rs @@ -255,6 +255,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(|| { diff --git a/lib/src/query.c b/lib/src/query.c index 612439ef5..bf4f1cbdf 100644 --- a/lib/src/query.c +++ b/lib/src/query.c @@ -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,