Skip to content

fix(grit): support positional arguments in pattern, function, and node calls#10281

Open
Zelys-DFKH wants to merge 2 commits into
biomejs:mainfrom
Zelys-DFKH:fix/gritql-positional-args
Open

fix(grit): support positional arguments in pattern, function, and node calls#10281
Zelys-DFKH wants to merge 2 commits into
biomejs:mainfrom
Zelys-DFKH:fix/gritql-positional-args

Conversation

@Zelys-DFKH
Copy link
Copy Markdown

Fixes #10114.

The GritQL spec says positional arguments are valid, so console_method_to_info(\log`)should work the same asconsole_method_to_info(method = `log`)`. Biome was rejecting them.

The fix needed two places. In the parser, parse_maybe_named_arg sent every GRIT_NAME token to parse_named_arg, which requires =. A positional call like is_log() would get misparsed as a broken named arg. One token of lookahead fixes it.

In the pattern compiler, node_to_args_pairs rejected any positional arg that wasn't a GritVariable — backtick snippets and other patterns were all refused with ExpectedVariable. The same restriction in node_pattern_from_node_with_name_and_kind blocked AST node patterns too. Both now resolve the parameter name by position.

AI assistance was used in writing this PR.

Test Plan

  • ok/positional_args.grit — the exact GritQL from the issue parses without errors
  • ok/positional_args_pattern_call.grit — positional pattern call (is_log()) parses correctly
  • specs/ts/positional_args.{grit,ts,snap} — end-to-end: the pattern compiles and matches console.log(...) but not console.warn(...)

…e calls

Fixes biomejs#10114. GritQL allows omitting parameter names when arguments are given in order, but Biome rejected all positional args.

- Parser: parse_maybe_named_arg now uses a one-token lookahead to distinguish a positional pattern call from a named arg, instead of unconditionally routing GRIT_NAME to parse_named_arg
- Compiler: node_to_args_pairs now accepts any AnyGritPattern as a positional arg and resolves the parameter name by position, instead of rejecting non-GritVariable args with ExpectedVariable
- Compiler: node_pattern_from_node_with_name_and_kind applies the same fix for AST node patterns
@github-actions github-actions Bot added A-Parser Area: parser L-Grit Language: GritQL labels May 6, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

Walkthrough

The change enables positional (unkeyed) arguments in GritQL patterns, functions, predicates and AST node patterns. The parser's parse_maybe_named_arg now treats a GRIT_NAME as a named argument only when followed by =, otherwise it parses a pattern. The pattern compiler was refactored to accept non-variable positional argument patterns by resolving parameters by position when names are omitted, and to derive parameter names from patterns when possible. Tests demonstrating positional calls were added.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarises the main change: adding support for positional arguments in GritQL pattern, function, and node calls.
Description check ✅ Passed The description clearly relates to the changeset, explaining the parser and compiler fixes needed to support positional arguments in GritQL.
Linked Issues check ✅ Passed The PR fully addresses issue #10114 by implementing positional argument support in the parser and pattern compiler, with test coverage for all scenarios.
Out of Scope Changes check ✅ Passed All changes are directly scoped to supporting positional arguments: parser refinement, compiler updates, and test additions with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs (1)

207-218: 💤 Low value

Tiny nit: expected_params.as_ref().map_or(0, |p| p.len()) could be 0 literally.

We only enter this ok_or_else when expected_params.as_ref().and_then(...) returned None. That's either because expected_params was None (→ 0) or because params.get(i) was out of range (→ params.len() <= i, but reporting params.len() is exactly what we want). So the current expression is correct — it's the expected_params is None case where reporting max_args: 0 feels a bit terse for an unknown callee. Not blocking; just flagging in case you'd rather route the None case through MissingArgumentName or skip the error here entirely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs` around
lines 207 - 218, The current ok_or_else builds
NodeLikeArgumentError::TooManyArguments with max_args computed as
expected_params.as_ref().map_or(0, |p| p.len()), which yields 0 when
expected_params is None; instead, first test expected_params: if
expected_params.is_none() return a clearer error such as
NodeLikeArgumentError::MissingArgumentName { name: fn_name.to_string() } and
only construct NodeLikeArgumentError::TooManyArguments when expected_params is
Some—using params.len() for max_args; update the branch around expected_params,
NodeLikeArgumentError::TooManyArguments, and fn_name in call_compiler.rs
accordingly.
.changeset/fix-gritql-positional-args.md (1)

5-5: ⚡ Quick win

Use the Fixed [#NUMBER](issue link): … prefix.

Issue #10114 exists for this fix, so per the changeset convention the description should lead with the linked-issue prefix instead of plain prose.

📝 Suggested tweak
-Fixed a bug where GritQL patterns rejected positional (unkeyed) arguments.
+Fixed [`#10114`](https://github.com/biomejs/biome/issues/10114): GritQL patterns rejected positional (unkeyed) arguments.

As per coding guidelines: "For bug fixes, start with 'Fixed [#NUMBER](issue link): ...'".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/fix-gritql-positional-args.md at line 5, Replace the changeset
description line so it follows the convention for bug fixes: change the current
sentence "Fixed a bug where GritQL patterns rejected positional (unkeyed)
arguments." to start with the linked-issue prefix, e.g. "Fixed [`#10114`](issue
link): GritQL patterns rejected positional (unkeyed) arguments.", ensuring the
issue number 10114 is used and the rest of the text remains the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.changeset/fix-gritql-positional-args.md:
- Line 5: Replace the changeset description line so it follows the convention
for bug fixes: change the current sentence "Fixed a bug where GritQL patterns
rejected positional (unkeyed) arguments." to start with the linked-issue prefix,
e.g. "Fixed [`#10114`](issue link): GritQL patterns rejected positional (unkeyed)
arguments.", ensuring the issue number 10114 is used and the rest of the text
remains the same.

In `@crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs`:
- Around line 207-218: The current ok_or_else builds
NodeLikeArgumentError::TooManyArguments with max_args computed as
expected_params.as_ref().map_or(0, |p| p.len()), which yields 0 when
expected_params is None; instead, first test expected_params: if
expected_params.is_none() return a clearer error such as
NodeLikeArgumentError::MissingArgumentName { name: fn_name.to_string() } and
only construct NodeLikeArgumentError::TooManyArguments when expected_params is
Some—using params.len() for max_args; update the branch around expected_params,
NodeLikeArgumentError::TooManyArguments, and fn_name in call_compiler.rs
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f760091-2a20-4acb-92c4-1768b9a35428

📥 Commits

Reviewing files that changed from the base of the PR and between 04e22a1 and 34a3094.

⛔ Files ignored due to path filters (3)
  • crates/biome_grit_parser/tests/grit_test_suite/ok/positional_args.grit.snap is excluded by !**/*.snap and included by **
  • crates/biome_grit_parser/tests/grit_test_suite/ok/positional_args_pattern_call.grit.snap is excluded by !**/*.snap and included by **
  • crates/biome_grit_patterns/tests/specs/ts/positional_args.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (8)
  • .changeset/fix-gritql-positional-args.md
  • crates/biome_grit_parser/src/parser/mod.rs
  • crates/biome_grit_parser/tests/grit_test_suite/ok/positional_args.grit
  • crates/biome_grit_parser/tests/grit_test_suite/ok/positional_args_pattern_call.grit
  • crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs
  • crates/biome_grit_patterns/src/pattern_compiler/node_like_compiler.rs
  • crates/biome_grit_patterns/tests/specs/ts/positional_args.grit
  • crates/biome_grit_patterns/tests/specs/ts/positional_args.ts

When a non-variable positional argument is passed to a function with no
known parameter list, emit MissingArgumentName instead of TooManyArguments
with max_args=0. When the parameter list is known but the index is out of
range, use the actual parameter count in TooManyArguments.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 6, 2026

🦋 Changeset detected

Latest commit: 7de0f51

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs (1)

174-228: 💤 Low value

Logic looks correct — consider deduping the positional fallback.

The two arms of the inner match &pattern share the same "look up expected_params[i], otherwise emit TooManyArguments (when params are known) or MissingArgumentName (when they aren't)" routine. Pulling that into a small closure tightens the code and keeps the two error policies in one place.

♻️ Sketch of a shared fallback helper
             Ok(AnyGritMaybeNamedArg::AnyGritPattern(pattern)) => {
+                let positional_name = |missing_var: &str| -> Result<String, NodeLikeArgumentError> {
+                    expected_params
+                        .as_ref()
+                        .and_then(|params| params.get(i))
+                        .map(|p| {
+                            p.strip_prefix(lang.metavariable_prefix())
+                                .unwrap_or(p)
+                                .to_owned()
+                        })
+                        .ok_or_else(|| {
+                            if let Some(params) = expected_params.as_ref() {
+                                NodeLikeArgumentError::TooManyArguments {
+                                    name: fn_name.to_string(),
+                                    max_args: params.len(),
+                                }
+                            } else {
+                                NodeLikeArgumentError::MissingArgumentName {
+                                    name: fn_name.to_string(),
+                                    variable: missing_var.to_owned(),
+                                }
+                            }
+                        })
+                };
                 let name = match &pattern {
                     AnyGritPattern::GritVariable(var) => {
                         let var_name = var.to_trimmed_string();
-                        let name = var_name
+                        match var_name
                             .strip_prefix(lang.metavariable_prefix())
                             .filter(|stripped| {
                                 expected_params.as_ref().is_none_or(|expected| {
                                     expected.iter().any(|exp| exp == &var_name || exp == stripped)
                                 })
                             })
-                            .or_else(|| { ... })
-                            .ok_or_else(|| { ... })?;
-                        name.to_owned()
+                        {
+                            Some(name) => name.to_owned(),
+                            None => positional_name(&var_name)?,
+                        }
                     }
-                    _ => { ... }
+                    _ => positional_name("")?,
                 };
                 Ok((name, pattern))
             }

Purely cosmetic — feel free to ignore if you'd rather not touch it again.

A couple of small things worth noting (no action required):

  • For a variable whose name doesn't match any expected param (e.g. a typo like $methdo), the new behaviour silently rebinds it positionally rather than raising UnknownVariable. That's the natural cost of supporting positional variable binding, and matches the issue's intent — just flagging it so you're not surprised by it later in a bug report.
  • Line 222's variable: String::new() only fires when expected_params is None and the positional arg is not a variable; downstream you'd typically hit UnknownFunctionOrPattern first, so it's effectively defensive. Leaving as-is is fine.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs` around
lines 174 - 228, The two branches inside the
AnyGritMaybeNamedArg::AnyGritPattern handling duplicate the logic for resolving
expected_params[i] and producing NodeLikeArgumentError variants; extract that
positional-fallback lookup into a small helper/closure (e.g., let
resolve_positional = |i: usize| -> Result<String, NodeLikeArgumentError> { ...
}) that captures expected_params and fn_name and returns the stripped name or
the appropriate TooManyArguments/MissingArgumentName error, then call this
helper from both the AnyGritPattern::GritVariable fallback path and the
non-variable arm to eliminate duplication and centralize the error policy.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs`:
- Around line 174-228: The two branches inside the
AnyGritMaybeNamedArg::AnyGritPattern handling duplicate the logic for resolving
expected_params[i] and producing NodeLikeArgumentError variants; extract that
positional-fallback lookup into a small helper/closure (e.g., let
resolve_positional = |i: usize| -> Result<String, NodeLikeArgumentError> { ...
}) that captures expected_params and fn_name and returns the stripped name or
the appropriate TooManyArguments/MissingArgumentName error, then call this
helper from both the AnyGritPattern::GritVariable fallback path and the
non-variable arm to eliminate duplication and centralize the error policy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 854eef94-010a-4f8b-9d4e-dc5a5e15cd8f

📥 Commits

Reviewing files that changed from the base of the PR and between 34a3094 and 7de0f51.

📒 Files selected for processing (1)
  • crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs

@dyc3 dyc3 added M-Likely Agent Meta: this was likely an automated PR without a human in the loop labels May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Parser Area: parser L-Grit Language: GritQL M-Likely Agent Meta: this was likely an automated PR without a human in the loop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 GritQL parser errors on unkeyed pattern/function/predicate arguments

2 participants