fix(query)!: assert that predicates end in ! or ?

Predicates/directives are documented to end in either `!` or `?`.
However, `query.c` allows them to be any valid identifier, and also
allows `?` or `!` characters anywhere inside an identifier.

This commit removes `?` and `!` from the list of valid identifier
characters, and asserts that predicates/directives only *end* in `?` or
`!`, respectively.

This commit is breaking because you can no longer do something like
`(#eq? @capture foo!bar)` (`foo!bar` must now be quoted).
This commit is contained in:
Riley Bruins 2025-06-03 18:41:12 +02:00 committed by Christian Clason
parent ac13c86675
commit 6cabd9e67f
2 changed files with 20 additions and 6 deletions

View file

@ -117,12 +117,24 @@ fn test_query_errors_on_invalid_syntax() {
// Unclosed sibling expression with predicate
assert_eq!(
Query::new(&language, r"((identifier) (#a)")
Query::new(&language, r"((identifier) (#a?)")
.unwrap_err()
.message,
[
"((identifier) (#a)", //
" ^",
"((identifier) (#a?)", //
" ^",
]
.join("\n")
);
// Predicate not ending in `?` or `!`
assert_eq!(
Query::new(&language, r"((identifier) (#a))")
.unwrap_err()
.message,
[
"((identifier) (#a))", //
" ^",
]
.join("\n")
);

View file

@ -411,9 +411,7 @@ static void stream_scan_identifier(Stream *stream) {
iswalnum(stream->next) ||
stream->next == '_' ||
stream->next == '-' ||
stream->next == '.' ||
stream->next == '?' ||
stream->next == '!'
stream->next == '.'
);
}
@ -2092,6 +2090,10 @@ static TSQueryError ts_query__parse_predicate(
if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
const char *predicate_name = stream->input;
stream_scan_identifier(stream);
if (stream->next != '?' && stream->next != '!') {
return TSQueryErrorSyntax;
}
stream_advance(stream);
uint32_t length = (uint32_t)(stream->input - predicate_name);
uint16_t id = symbol_table_insert_name(
&self->predicate_values,