Skip to content

fix(lint/useExpect): reject asymmetric matchers and utilities as assertions#9455

Merged
dyc3 merged 7 commits intobiomejs:mainfrom
omar-y-abdi:fix/use-expect-false-negatives
Mar 20, 2026
Merged

fix(lint/useExpect): reject asymmetric matchers and utilities as assertions#9455
dyc3 merged 7 commits intobiomejs:mainfrom
omar-y-abdi:fix/use-expect-false-negatives

Conversation

@omar-y-abdi
Copy link
Contributor

@omar-y-abdi omar-y-abdi commented Mar 12, 2026

Summary

Closes #9174

useExpect produced false negatives for Vitest constructs like expect.stringContaining(), expect.extend(), and expect.not.objectContaining(). These are asymmetric matchers and utilities, not assertions, but the rule counted them as valid assertions and did not flag the test.

The root cause was in is_expect_expression in playwright.rs. The JsStaticMemberExpression branch unconditionally recursed on the object expression, so any expect.anything() pattern was treated as a valid assertion chain.

Fix

When the object of a static member expression is a bare expect identifier (not a call like expect(value)), the member name is now checked against an allowlist of assertion-qualifying members: soft, poll, assertions, hasAssertions. All other members on bare expect (asymmetric matchers, extend, not prefix for negated matchers) are correctly rejected.

Chained assertions like expect(value).not.toBe(1) and expect.soft(page).toBeVisible() are unaffected because their object is a call expression, not a bare identifier.

Test plan

  • Added invalid/asymmetric-matchers.js with 3 cases: expect.stringContaining(), expect.extend(), expect.not.objectContaining() — all now correctly flagged
  • Added valid/assertion-counts.js with 5 cases: expect.assertions(), expect.hasAssertions(), expect.assertions(0), expect.soft(), expect.poll() — all correctly pass
  • All 6 use_expect spec tests pass
  • All 7 is_expect_call unit tests pass (no regression)
  • All 66 no_playwright_* spec tests pass (no regression)
  • All 2 no_conditional_expect spec tests pass (no regression)

Closes #9174

AI disclosure

This PR was written with assistance from Claude Code (code comprehension, tracing the bug through the AST traversal logic, and drafting the implementation). All code was reviewed and verified by the author.

…rtions

is_expect_expression treated any expect.XYZ() pattern as a valid assertion
because the JsStaticMemberExpression branch unconditionally recursed on the
object. This caused false negatives for expect.stringContaining(),
expect.extend(), expect.not.objectContaining(), and similar constructs that
are not assertions.

When the object is a bare expect identifier, check the member name against
an allowlist of assertion-qualifying members (soft, poll, assertions,
hasAssertions). Everything else on bare expect is now correctly rejected.

Closes biomejs#9174
@changeset-bot
Copy link

changeset-bot bot commented Mar 12, 2026

🦋 Changeset detected

Latest commit: ee548ac

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-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Mar 12, 2026
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.

Changeset is missing. The link to how to create was in the PR template, which was wiped out. I leave that to you

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 129c3165-c606-4ccd-ba93-bf78c219cf8b

📥 Commits

Reviewing files that changed from the base of the PR and between e0b56c3 and ee548ac.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/frameworks/playwright.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_js_analyze/src/frameworks/playwright.rs

Walkthrough

Tightens detection in the useExpect lint rule: static expect member accesses are now counted as assertions only when the member name is exactly soft, poll, assertions, or hasAssertions. Other expect.XYZ usages (including asymmetric matchers like expect.stringContaining and helpers like expect.extend) are no longer treated as assertions. Adds tests covering invalid asymmetric-matcher usages and valid assertion-count APIs. No exported or public signatures changed.

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: rejecting asymmetric matchers and utilities as valid assertions in the useExpect lint rule.
Description check ✅ Passed The description thoroughly explains the problem, root cause, fix, and test coverage, clearly relating to the changeset.
Linked Issues check ✅ Passed The PR addresses all primary coding objectives from issue #9174: rejects asymmetric matchers and utilities while preserving valid assertion syntaxes, with comprehensive test cases.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the useExpect rule as specified in issue #9174; no extraneous modifications detected.

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

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

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_analyze/tests/specs/nursery/useExpect/invalid/asymmetric-matchers.js (1)

3-17: Consider adding a few more asymmetric matchers for completeness (optional).

The fix rejects all non-allowlisted members generically, so exhaustive testing isn't essential. However, adding one or two more cases (e.g., expect.anything(), expect.arrayContaining()) would strengthen confidence and serve as documentation of the intended behaviour.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@crates/biome_js_analyze/tests/specs/nursery/useExpect/invalid/asymmetric-matchers.js`
around lines 3 - 17, Add one or two extra test cases that use other asymmetric
matchers to broaden coverage: e.g., create a test similar to "only asymmetric
matcher, not an assertion" that assigns expect.anything() to a variable and
another that assigns expect.arrayContaining([1,2]) (or expect.objectContaining
with nested asymmetric) to exercise arrayContaining; place them near the
existing tests (functions test/it using matcher variables) so names like "only
asymmetric matcher, not an assertion" / "negated asymmetric matcher is not an
assertion" are extended with new cases and the new matcher variables mirror the
existing pattern.
🤖 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_analyze/tests/specs/nursery/useExpect/invalid/asymmetric-matchers.js`:
- Around line 3-17: Add one or two extra test cases that use other asymmetric
matchers to broaden coverage: e.g., create a test similar to "only asymmetric
matcher, not an assertion" that assigns expect.anything() to a variable and
another that assigns expect.arrayContaining([1,2]) (or expect.objectContaining
with nested asymmetric) to exercise arrayContaining; place them near the
existing tests (functions test/it using matcher variables) so names like "only
asymmetric matcher, not an assertion" / "negated asymmetric matcher is not an
assertion" are extended with new cases and the new matcher variables mirror the
existing pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d2c01fad-b148-49f2-9340-d672981fa972

📥 Commits

Reviewing files that changed from the base of the PR and between b881fea and c56c646.

⛔ Files ignored due to path filters (2)
  • crates/biome_js_analyze/tests/specs/nursery/useExpect/invalid/asymmetric-matchers.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useExpect/valid/assertion-counts.js.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • crates/biome_js_analyze/src/frameworks/playwright.rs
  • crates/biome_js_analyze/tests/specs/nursery/useExpect/invalid/asymmetric-matchers.js
  • crates/biome_js_analyze/tests/specs/nursery/useExpect/valid/assertion-counts.js

@omar-y-abdi
Copy link
Contributor Author

Addressed both review items:

  • Replaced the issue reference in the comment with a link to the Vitest asymmetric matchers docs
  • Added changeset file

@omar-y-abdi omar-y-abdi requested a review from ematipico March 12, 2026 08:44
Copy link
Contributor Author

@omar-y-abdi omar-y-abdi left a comment

Choose a reason for hiding this comment

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

Happy to help out

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 12, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing omar-y-abdi:fix/use-expect-false-negatives (c221d57) with main (4305e9d)

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.

@omar-y-abdi
Copy link
Contributor Author

@ematipico Hey, this is ready to merge whenever you get a chance.

@dyc3
Copy link
Contributor

dyc3 commented Mar 17, 2026

@omar-y-abdi there is still failing CI

@dyc3 dyc3 merged commit 1710cf1 into biomejs:main Mar 20, 2026
3 checks passed
@github-actions github-actions bot mentioned this pull request Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 useExpect produces false negatives for Vitest expect.XYZ functions that are not valid assertions

4 participants