Skip to content

fix(noDuplicateTestHooks): check for options argument#8934

Merged
ematipico merged 14 commits intobiomejs:mainfrom
tim-we:fix-duplicate-test-hooks-rule
Feb 26, 2026
Merged

fix(noDuplicateTestHooks): check for options argument#8934
ematipico merged 14 commits intobiomejs:mainfrom
tim-we:fix-duplicate-test-hooks-rule

Conversation

@tim-we
Copy link
Contributor

@tim-we tim-we commented Feb 1, 2026

Summary

Right now biome does not correctly identify this pattern

test("foo", { retry: 3 }, () => {});

where the second argument is an options object.
This is where you put options in Vitest so it should be supported by biome.

This PR fixes issue #8265.

This is still work in progress.

AI assistance notice

I used Claude Code to help me understand the codebase and help me where my Rust knowledge got a little bit rusty. It also helped me updating the comments. The solution is my own.

Test Plan

I added tests in crates/biome_js_syntax/src/expr_ext.rs.

Docs

Not a new rule or an option so no documentation changes (so far)

@changeset-bot
Copy link

changeset-bot bot commented Feb 1, 2026

🦋 Changeset detected

Latest commit: ecc0c6c

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

@github-actions github-actions bot added A-Parser Area: parser L-JavaScript Language: JavaScript and super languages labels Feb 1, 2026
@tim-we tim-we force-pushed the fix-duplicate-test-hooks-rule branch from eef298d to 888fa41 Compare February 1, 2026 15:16
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 1, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing tim-we:fix-duplicate-test-hooks-rule (ecc0c6c) with main (1f2fe2e)

Open in CodSpeed

Footnotes

  1. 156 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@tim-we tim-we force-pushed the fix-duplicate-test-hooks-rule branch from 7a6b2f8 to 2a7ca69 Compare February 17, 2026 22:58
@ematipico
Copy link
Member

This code is also used by our formatter, so make sure to add a test for it, and track it in the changelog

@tim-we tim-we force-pushed the fix-duplicate-test-hooks-rule branch 2 times, most recently from 38d85c2 to d8b9652 Compare February 22, 2026 21:12
@github-actions github-actions bot added the A-Formatter Area: formatter label Feb 22, 2026
@tim-we tim-we marked this pull request as ready for review February 22, 2026 21:29
@tim-we
Copy link
Contributor Author

tim-we commented Feb 22, 2026

@ematipico thanks for the reminder. I added a tests and added this to the changelog. Sorry for taking so long, I did not find much time for this. The PR is ready for review now.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR updates test-call detection in the syntax crate to recognise TestOptions-style 3-argument patterns (e.g., describe("name", { retry: 2 }, () => {})) and 2-/3-argument callback shapes. It adds helpers to detect object option arguments and function callbacks with block bodies and ≤1 parameter, refines is_test_call_expression to accept the new shapes while tightening checks for duration-style third args, expands positive and negative tests for these patterns, and adds a changeset entry marking the bug fix.

Possibly related PRs

Suggested labels

A-Linter

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: fixing the noDuplicateTestHooks rule to properly handle test calls with options arguments.
Description check ✅ Passed The description clearly explains the problem (unrecognized TestOptions pattern in test calls), references the relevant issue, and outlines the solution with test additions and changelog updates.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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_js_syntax/src/expr_ext.rs (1)

2372-2378: Good positive test cases for the new pattern. Consider adding a couple of negative cases to guard against false positives — e.g. test('foo', { retry: 3 }) (options without callback) and test('foo', { retry: 3 }, 42) (options with non-function third arg) should both return Ok(false).

Suggested negative test cases
         let call_expression =
             extract_call_expression("describe.only('bar', { timeout: 42*1000 }, () => {})");
         assert_eq!(call_expression.is_test_call_expression(), Ok(true));
+
+        // Negative cases for TestOptions pattern
+        let call_expression = extract_call_expression("test('foo', { retry: 3 })");
+        assert_eq!(call_expression.is_test_call_expression(), Ok(false));
+
+        let call_expression = extract_call_expression("test('foo', { retry: 3 }, 42)");
+        assert_eq!(call_expression.is_test_call_expression(), Ok(false));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_syntax/src/expr_ext.rs` around lines 2372 - 2378, Add
negative unit tests to ensure is_test_call_expression doesn't false-positive:
call extract_call_expression with "test('foo', { retry: 3 })" and with
"test('foo', { retry: 3 }, 42)" and assert that
call_expression.is_test_call_expression() returns Ok(false) for both; place
these alongside the existing positive tests near the extract_call_expression
usages so the tests cover options-without-callback and
options-with-non-function-third-arg cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Around line 2372-2378: Add negative unit tests to ensure
is_test_call_expression doesn't false-positive: call extract_call_expression
with "test('foo', { retry: 3 })" and with "test('foo', { retry: 3 }, 42)" and
assert that call_expression.is_test_call_expression() returns Ok(false) for
both; place these alongside the existing positive tests near the
extract_call_expression usages so the tests cover options-without-callback and
options-with-non-function-third-arg cases.

Copy link
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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_js_syntax/src/expr_ext.rs (1)

2025-2025: ⚠️ Potential issue | 🟡 Minor

Pre-existing doc-link typo — easy fix while you're here.

The link [function expression]: crate::JsCallArgumentList resolves to JsCallArgumentList rather than JsFunctionExpression.

📝 One-line fix
-/// [function expression]: crate::JsCallArgumentList
+/// [function expression]: crate::JsFunctionExpression
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_syntax/src/expr_ext.rs` at line 2025, The doc-link for the
item incorrectly points to JsCallArgumentList; update the markdown reference
label so `[function expression]` resolves to `crate::JsFunctionExpression`
instead of `crate::JsCallArgumentList` in expr_ext.rs (replace the target symbol
`JsCallArgumentList` with `JsFunctionExpression` for the `[function expression]:
...` link).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Around line 2372-2387: Add two edge-case tests to exercise the TestOptions
branch: one where the callback is an expression-body arrow function (e.g.,
test('x', {retry:1}, () => 1)) and assert is_test_call_expression() returns
Ok(false) to cover the is_function_with_block_body arrow-body path, and another
where the callback is a regular function expression (e.g., test('x', {retry:1},
function() {})) and assert is_test_call_expression() returns Ok(true) to hit the
JsFunctionExpression branch used by is_function_with_block_body; place these
near the existing TestOptions positive/negative cases so they exercise
is_test_call_expression and its is_function_with_block_body logic.
- Around line 2085-2095: The change restricts the 3-arg pattern by using
is_function_with_block_body(&second) which now rejects arrow-expression bodies
and multi-parameter functions (e.g., test("x", x => x, 1000)) causing a
behavioral break; add a brief inline comment next to the
is_function_with_block_body check explaining that the 3-argument form
intentionally requires a block-bodied single-param function (and that the 2-arg
form remains permissive to match Prettier), and add a negative unit test that
asserts the pattern returns false for cases like an expression-bodied arrow or
multi-param function passed as the second argument with a numeric third to lock
the new semantics (reference the is_function_with_block_body check and the
matching branch that returns Ok(matches!(...)) to locate where to add the
comment and where to add the test).

---

Outside diff comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Line 2025: The doc-link for the item incorrectly points to JsCallArgumentList;
update the markdown reference label so `[function expression]` resolves to
`crate::JsFunctionExpression` instead of `crate::JsCallArgumentList` in
expr_ext.rs (replace the target symbol `JsCallArgumentList` with
`JsFunctionExpression` for the `[function expression]: ...` link).

@tim-we tim-we force-pushed the fix-duplicate-test-hooks-rule branch from 6dd7f2d to a20bc5c Compare February 25, 2026 21:56
Copy link
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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Line 2373: Typo in the test comment: change the misspelled "TesOptions" to
"TestOptions" in the comment that precedes the positive cases for the
TestOptions pattern (search for the comment text "Positive cases for TesOptions
pattern" near the tests for TestOptions). Update only the comment text to the
correct spelling to avoid confusion.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dd7f2d and a20bc5c.

⛔ Files ignored due to path filters (1)
  • crates/biome_js_formatter/tests/specs/js/module/declarations/test_declaration.js.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • .changeset/grumpy-zebras-sort.md
  • crates/biome_js_formatter/tests/specs/js/module/declarations/test_declaration.js
  • crates/biome_js_syntax/src/expr_ext.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/grumpy-zebras-sort.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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.

♻️ Duplicate comments (1)
crates/biome_js_syntax/src/expr_ext.rs (1)

2373-2373: ⚠️ Potential issue | 🟡 Minor

TesOptionsTestOptions (typo still present).

✏️ One-character fix
-        // Positive cases for TesOptions pattern
+        // Positive cases for TestOptions pattern
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_syntax/src/expr_ext.rs` at line 2373, Fix the one-character
typo in the comment that reads "TesOptions" so it correctly reads "TestOptions"
(the comment near the "Positive cases for TestOptions pattern" section in
expr_ext.rs). Locate the comment containing the misspelled identifier
"TesOptions" and update the text to "TestOptions" to match the actual type/name
used elsewhere.
🧹 Nitpick comments (1)
crates/biome_js_syntax/src/expr_ext.rs (1)

2086-2095: None arm in the inner matches! is unreachable.

After the len == 2 early-return on line 2082, the code that follows only runs when len == 3, so third is always Some(…) here. The None branch is dead.

✂️ Optional cleanup
-                    return Ok(matches!(
-                        third,
-                        None | Some(Ok(AnyJsCallArgument::AnyJsExpression(
-                            AnyJsLiteralExpression(
-                                self::AnyJsLiteralExpression::JsNumberLiteralExpression(_)
-                            )
-                        )))
-                    ));
+                    return Ok(matches!(
+                        third,
+                        Some(Ok(AnyJsCallArgument::AnyJsExpression(
+                            AnyJsLiteralExpression(
+                                self::AnyJsLiteralExpression::JsNumberLiteralExpression(_)
+                            )
+                        )))
+                    ));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_syntax/src/expr_ext.rs` around lines 2086 - 2095, The inner
matches! includes a redundant None arm because the surrounding logic (after the
len == 2 early return) guarantees len == 3, so third is always Some(...); update
the check in the block guarded by is_function_with_block_body(&second)? to only
match the Some(Ok(...)) case (e.g., replace the matches! that allows None with a
direct Some(Ok(...)) pattern or an if let Some(Ok(...)) check) so the
unreachable None branch is removed; touch the matches! invocation that
references third and the
AnyJsCallArgument/AnyJsLiteralExpression/JsNumberLiteralExpression pattern to
perform this simplification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Line 2373: Fix the one-character typo in the comment that reads "TesOptions"
so it correctly reads "TestOptions" (the comment near the "Positive cases for
TestOptions pattern" section in expr_ext.rs). Locate the comment containing the
misspelled identifier "TesOptions" and update the text to "TestOptions" to match
the actual type/name used elsewhere.

---

Nitpick comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Around line 2086-2095: The inner matches! includes a redundant None arm
because the surrounding logic (after the len == 2 early return) guarantees len
== 3, so third is always Some(...); update the check in the block guarded by
is_function_with_block_body(&second)? to only match the Some(Ok(...)) case
(e.g., replace the matches! that allows None with a direct Some(Ok(...)) pattern
or an if let Some(Ok(...)) check) so the unreachable None branch is removed;
touch the matches! invocation that references third and the
AnyJsCallArgument/AnyJsLiteralExpression/JsNumberLiteralExpression pattern to
perform this simplification.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a20bc5c and 114fb36.

📒 Files selected for processing (1)
  • crates/biome_js_syntax/src/expr_ext.rs

Copy link
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.

♻️ Duplicate comments (1)
crates/biome_js_syntax/src/expr_ext.rs (1)

2373-2373: ⚠️ Potential issue | 🟡 Minor

Typo in test comment — TesOptionsTestOptions.

✏️ Proposed fix
-        // Positive cases for TesOptions pattern
+        // Positive cases for TestOptions pattern
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_syntax/src/expr_ext.rs` at line 2373, Update the inline test
comment that currently reads "TesOptions" to the correct "TestOptions" so the
comment correctly describes the positive cases for the TestOptions pattern;
locate the comment containing "Positive cases for TesOptions pattern" (near the
TestOptions test cases in expr_ext.rs) and correct the typo.
🧹 Nitpick comments (1)
crates/biome_js_syntax/src/expr_ext.rs (1)

2372-2394: Consider adding a test for the single-binding unparenthesised arrow callback form.

The existing positive cases only exercise () => {} (zero params). Adding a case for a single unparenthesised param (x => {}) would directly exercise the AnyJsBinding path in is_function_with_block_body and confirm the len() concern raised above.

📝 Suggested additional assertion
 let call_expression =
     extract_call_expression("it('bar', { timeout: 5000 }, function() {})");
 assert_eq!(call_expression.is_test_call_expression(), Ok(true));
+
+// Single-binding (unparenthesised) arrow with block body
+let call_expression =
+    extract_call_expression("test('foo', { retry: 1 }, x => {})");
+assert_eq!(call_expression.is_test_call_expression(), Ok(true));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_syntax/src/expr_ext.rs` around lines 2372 - 2394, Add a
positive test that covers the single-binding unparenthesised arrow callback to
exercise the AnyJsBinding path in is_function_with_block_body: create a
call_expression using extract_call_expression with a third argument like an
unparenthesised arrow (e.g. x => { }) passed to test(...) and assert that
call_expression.is_test_call_expression() returns Ok(true); place this assertion
alongside the other positive TestOptions pattern cases to validate the len()
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Line 2373: Update the inline test comment that currently reads "TesOptions" to
the correct "TestOptions" so the comment correctly describes the positive cases
for the TestOptions pattern; locate the comment containing "Positive cases for
TesOptions pattern" (near the TestOptions test cases in expr_ext.rs) and correct
the typo.

---

Nitpick comments:
In `@crates/biome_js_syntax/src/expr_ext.rs`:
- Around line 2372-2394: Add a positive test that covers the single-binding
unparenthesised arrow callback to exercise the AnyJsBinding path in
is_function_with_block_body: create a call_expression using
extract_call_expression with a third argument like an unparenthesised arrow
(e.g. x => { }) passed to test(...) and assert that
call_expression.is_test_call_expression() returns Ok(true); place this assertion
alongside the other positive TestOptions pattern cases to validate the len()
handling.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 114fb36 and 5bd52b2.

📒 Files selected for processing (1)
  • crates/biome_js_syntax/src/expr_ext.rs

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

thank you !

@ematipico ematipico merged commit b49707c into biomejs:main Feb 26, 2026
17 checks passed
@github-actions github-actions bot mentioned this pull request Feb 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter A-Parser Area: parser L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants