mirror of
https://github.com/github/codeql.git
synced 2026-08-01 07:53:00 +02:00
Three improvements to the query parser, all aimed at allowing query
patterns to refer to unnamed tokens:
1. Bare-literal capture: `"=" @op` now captures the unnamed `=` token,
matching the parenthesized form `("=") @op`. Previously the literal
branch in parse_query_list skipped the maybe_wrap_capture call, so
the `@op` was a leftover token and would error.
2. Bare `_` matches any node, named or unnamed. Previously bare `_` and
`(_)` both produced QueryNode::Any with the same matches_named_only
behaviour, so bare `_` would skip unnamed children. Now Any carries a
match_unnamed flag: false for `(_)` (named-only, tree-sitter default)
and true for bare `_` (any node).
3. Named fields and bare child patterns may be intermixed in any order.
Previously, once parse_query_fields saw a bare pattern it would stop
accepting named fields. The fix accumulates bare patterns into the
implicit `child` field and keeps parsing.
Each named field independently selects its target field for matching, so
the source-order of fields in the query is purely cosmetic and intermixing
is safe.
Add tests covering parenthesized capture, bare-literal capture, and the
named-vs-any distinction between `(_)` and bare `_`. Update query-syntax
docs to reflect all three.
113 lines
3.9 KiB
Rust
113 lines
3.9 KiB
Rust
use proc_macro::TokenStream;
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
|
|
mod parse;
|
|
|
|
/// Proc macro for constructing a `QueryNode` from a tree-sitter-inspired pattern.
|
|
///
|
|
/// # Syntax
|
|
///
|
|
/// ```text
|
|
/// (_) - match any named node (skips unnamed tokens)
|
|
/// _ - match any node, named or unnamed
|
|
/// (kind) - match a named node of the given kind
|
|
/// ("literal") - match an unnamed token by its text
|
|
/// "literal" - shorthand for `("literal")`
|
|
/// (kind field: (pattern)) - match with named field
|
|
/// (kind field: _) - bare `_` and bare literals work in field position too
|
|
/// (kind (pat) (pat)...) - match unnamed children
|
|
/// (pattern) @capture - capture the matched node
|
|
/// "literal" @capture - capture an unnamed token
|
|
/// _ @capture - capture any node
|
|
/// (pattern)* @capture - capture each repeated match
|
|
/// (pattern)? - zero or one
|
|
/// ```
|
|
///
|
|
/// Named fields and bare child patterns may be intermixed in any order.
|
|
#[proc_macro]
|
|
pub fn query(input: TokenStream) -> TokenStream {
|
|
let input2: TokenStream2 = input.into();
|
|
match parse::parse_query_top(input2) {
|
|
Ok(output) => output.into(),
|
|
Err(err) => err.to_compile_error().into(),
|
|
}
|
|
}
|
|
|
|
/// Build a single AST node from a template, returning its `Id`.
|
|
///
|
|
/// # Template syntax
|
|
///
|
|
/// ```text
|
|
/// (kind "literal") - leaf with static content
|
|
/// (kind #{expr}) - leaf with computed content (expr.to_string())
|
|
/// (kind $fresh) - leaf with auto-generated unique name
|
|
/// {expr} - embed a Rust expression returning Id
|
|
/// {..expr} - splice an iterable of Id (in child/field position)
|
|
/// field: {..expr} - splice into a named field
|
|
/// ```
|
|
///
|
|
/// Can be called with an explicit context or using the implicit context
|
|
/// from an enclosing `rule!`:
|
|
///
|
|
/// ```text
|
|
/// tree!(ctx, (kind ...)) // explicit BuildCtx
|
|
/// tree!((kind ...)) // implicit context from rule!
|
|
/// ```
|
|
#[proc_macro]
|
|
pub fn tree(input: TokenStream) -> TokenStream {
|
|
let input2: TokenStream2 = input.into();
|
|
match parse::parse_tree_top(input2) {
|
|
Ok(output) => output.into(),
|
|
Err(err) => err.to_compile_error().into(),
|
|
}
|
|
}
|
|
|
|
/// Build a list of AST nodes from a template, returning `Vec<Id>`.
|
|
///
|
|
/// Like `tree!` but returns `Vec<Id>` and supports multiple top-level
|
|
/// elements. All syntax from `tree!` is available.
|
|
///
|
|
/// Can be called with an explicit context or using the implicit context
|
|
/// from an enclosing `rule!`:
|
|
///
|
|
/// ```text
|
|
/// trees!(ctx, (node1 ...) (node2 ...)) // explicit BuildCtx
|
|
/// trees!((node1 ...) (node2 ...)) // implicit context from rule!
|
|
/// ```
|
|
#[proc_macro]
|
|
pub fn trees(input: TokenStream) -> TokenStream {
|
|
let input2: TokenStream2 = input.into();
|
|
match parse::parse_trees_top(input2) {
|
|
Ok(output) => output.into(),
|
|
Err(err) => err.to_compile_error().into(),
|
|
}
|
|
}
|
|
|
|
/// Define a desugaring rule with query and transform in one declaration.
|
|
///
|
|
/// ```text
|
|
/// rule!(
|
|
/// (query_pattern field: (_) @name (kind)* @repeated (_)? @optional)
|
|
/// =>
|
|
/// (output_template field: {name} {..repeated})
|
|
/// )
|
|
///
|
|
/// // Shorthand: captures become fields on the output node
|
|
/// rule!((query ...) => output_kind)
|
|
/// ```
|
|
///
|
|
/// Captures become Rust variables automatically:
|
|
/// - `@name` (no quantifier) → `name: Id`
|
|
/// - `@name` (after `*`/`+`) → `name: Vec<Id>`
|
|
/// - `@name` (after `?`) → `name: Option<Id>`
|
|
///
|
|
/// `tree!` and `trees!` can be used without explicit context inside `{...}`.
|
|
#[proc_macro]
|
|
pub fn rule(input: TokenStream) -> TokenStream {
|
|
let input2: TokenStream2 = input.into();
|
|
match parse::parse_rule_top(input2) {
|
|
Ok(output) => output.into(),
|
|
Err(err) => err.to_compile_error().into(),
|
|
}
|
|
}
|