diff --git a/cli/src/tests/query_test.rs b/cli/src/tests/query_test.rs index 30d8e9c9..6a0e7075 100644 --- a/cli/src/tests/query_test.rs +++ b/cli/src/tests/query_test.rs @@ -119,6 +119,19 @@ fn test_query_errors_on_invalid_syntax() { .join("\n") ); + // Need at least one child node for a child anchor + assert_eq!( + Query::new(language, r#"(statement_block .)"#) + .unwrap_err() + .message, + [ + // + r#"(statement_block .)"#, + r#" ^"# + ] + .join("\n") + ); + // tree-sitter/tree-sitter/issues/968 assert_eq!( Query::new(get_language("c"), r#"(parameter_list [ ")" @foo)"#) @@ -855,6 +868,32 @@ fn test_query_matches_with_immediate_siblings() { }); } +#[test] +fn test_query_matches_with_last_named_child() { + allocations::record(|| { + let language = get_language("c"); + let query = Query::new( + language, + "(compound_statement + (_) + (_) + (expression_statement + (identifier) @last_id) .)", + ) + .unwrap(); + assert_query_matches( + language, + &query, + " + void one() { a; b; c; } + void two() { d; e; } + void three() { f; g; h; i; } + ", + &[(0, vec![("last_id", "c")]), (0, vec![("last_id", "i")])], + ); + }); +} + #[test] fn test_query_matches_with_repeated_leaf_nodes() { allocations::record(|| { diff --git a/lib/src/query.c b/lib/src/query.c index eebae855..bdf2d745 100644 --- a/lib/src/query.c +++ b/lib/src/query.c @@ -1690,7 +1690,7 @@ static TSQueryError ts_query__parse_pattern( // Parse the child patterns bool child_is_immediate = false; - uint16_t child_start_step_index = self->steps.size; + uint16_t last_child_step_index = 0; for (;;) { if (stream->next == '.') { child_is_immediate = true; @@ -1698,6 +1698,7 @@ static TSQueryError ts_query__parse_pattern( stream_skip_whitespace(stream); } + uint16_t step_index = self->steps.size; TSQueryError e = ts_query__parse_pattern( self, stream, @@ -1706,7 +1707,10 @@ static TSQueryError ts_query__parse_pattern( ); if (e == PARENT_DONE && stream->next == ')') { if (child_is_immediate) { - self->steps.contents[child_start_step_index].is_last_child = true; + if (last_child_step_index == 0) { + return TSQueryErrorSyntax; + } + self->steps.contents[last_child_step_index].is_last_child = true; } stream_advance(stream); break; @@ -1714,6 +1718,7 @@ static TSQueryError ts_query__parse_pattern( return e; } + last_child_step_index = step_index; child_is_immediate = false; } }