Skip to content

Comments

feat(linter): implement vitest/no-unneeded-async-expect-function rule#17494

Merged
camc314 merged 5 commits intooxc-project:mainfrom
amondnet:feat/vitest-no-unneeded-async-expect-function
Jan 6, 2026
Merged

feat(linter): implement vitest/no-unneeded-async-expect-function rule#17494
camc314 merged 5 commits intooxc-project:mainfrom
amondnet:feat/vitest-no-unneeded-async-expect-function

Conversation

@amondnet
Copy link
Contributor

@amondnet amondnet commented Dec 30, 2025

Summary

Implement the no-unneeded-async-expect-function vitest rule which detects unnecessary async function wrappers in expect() calls.

Features

  • Detects async function wrappers (arrow functions and function expressions) inside expect() calls
  • Checks if the async wrapper contains only a single await expression
  • Auto-fix capability to replace the wrapper with the unwrapped call
  • Handles edge cases:
    • Block body (async () => { await doSomething(); })
    • Expression body (async () => await doSomething())
    • Function expressions (async function() { await doSomething(); })
    • Works with both rejects and resolves matchers

Example

// Bad
await expect(async () => await doSomething()).rejects.toThrow();
await expect(async () => { await doSomething(); }).rejects.toThrow();

// Good
await expect(doSomething()).rejects.toThrow();

Test Cases

10 passing test cases and 6 failing test cases covering:

  • Direct promise passing (correct usage)
  • Multiple statements in wrapper (can't simplify)
  • No await in wrapper (can't simplify)
  • Variable declaration before await
  • For-loop with await
  • Block body with single await
  • Expression body with await
  • Function expression
  • With function arguments
  • Promise.all wrapped

Test Plan

  • All existing linter tests pass
  • New no_unneeded_async_expect_function test passes
  • Clippy passes without warnings

Related

Reference: https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-unneeded-async-expect-function.md

https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/src/rules/no-unneeded-async-expect-function.ts
https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/tests/no-unneeded-async-expect-function.test.ts

#4656


This PR was generated with AI assistance. The implementation has been thoroughly reviewed and tested.

Copilot AI review requested due to automatic review settings December 30, 2025 15:51
@amondnet amondnet requested a review from camc314 as a code owner December 30, 2025 15:51
@github-actions github-actions bot added A-linter Area - Linter C-enhancement Category - New feature or request labels Dec 30, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements the no-unneeded-async-expect-function vitest linter rule, which detects and auto-fixes unnecessary async function wrappers in expect() calls when they only contain a single awaited expression.

Key Changes:

  • Detects async function wrappers (arrow and function expressions) that only contain await someCall()
  • Provides auto-fix to replace the wrapper with the direct call
  • Handles both block body and expression body arrow functions

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
crates/oxc_linter/src/rules/vitest/no_unneeded_async_expect_function.rs Main rule implementation with detection logic, auto-fix, helper functions, and comprehensive test suite
crates/oxc_linter/src/rules.rs Module declaration for the new rule
crates/oxc_linter/src/generated/rule_runner_impls.rs Generated RuleRunner implementation configured to use RunOnJestNode
crates/oxc_linter/src/snapshots/vitest_no_unneeded_async_expect_function.snap Test snapshot showing diagnostic output for all failing test cases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@connorshea
Copy link
Member

Based on the PR description and code comments I presume this was - at least in large part - generated using AI tooling.

Please ensure that the PR description is updated to note the AI usage in accordance with our policy, preferably including models/tools used and a confirmation that you have personally reviewed the code generated and ensured it works/makes sense: https://oxc.rs/docs/contribute/introduction.html#ai-usage-policy

@camc314 camc314 self-assigned this Dec 30, 2025
@Afsoon
Copy link
Contributor

Afsoon commented Jan 1, 2026

The test looks that are AI generated. In the eslint plugin, most of the expects are wrapped inside a it, and in this PR are simple expect.

@camc314 camc314 marked this pull request as draft January 2, 2026 17:11
Copy link
Contributor

@camc314 camc314 left a comment

Choose a reason for hiding this comment

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

Thanks for working on this.

I have re-ported the test cases from the original rule. A significant number of them are failing, these should be passing before this can be merged.

I've converted this to a draft so I can keep track of what needs my review and what doesn't. Please mark ready for review once you've fixed all cases and reviewed any AI generated code.

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 2, 2026

CodSpeed Performance Report

Merging #17494 will not alter performance

Comparing amondnet:feat/vitest-no-unneeded-async-expect-function (993a8d5) with main (f3767ea)

Summary

✅ 4 untouched
⏩ 41 skipped1

Footnotes

  1. 41 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.

@amondnet amondnet marked this pull request as ready for review January 5, 2026 00:16
@amondnet
Copy link
Contributor Author

amondnet commented Jan 5, 2026

@camc314
Thank you for re-porting the test cases and for the review!

I've fixed the failing tests by adding .with_vitest_plugin(true) to the Tester setup. The issue was that without this, the vitest framework context wasn't enabled, so the rule's run_on_jest_node method was never called, causing the test cases to incorrectly pass.

I've reviewed the AI-generated code and ensured it follows oxc patterns.

Ready for re-review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@amondnet amondnet force-pushed the feat/vitest-no-unneeded-async-expect-function branch from 2710c23 to 94f65f0 Compare January 5, 2026 10:33
amondnet and others added 4 commits January 6, 2026 19:14
Add rule to detect and auto-fix unnecessary async function wrappers
in expect() calls. When the only statement in an async wrapper is
`await someCall()`, the wrapper is unnecessary and the promise can
be passed directly to expect.

Example:
```js
// Bad
await expect(async () => await doSomething()).rejects.toThrow();

// Good
await expect(doSomething()).rejects.toThrow();
```

Closes oxc-project#9
- Add missing test cases for better coverage:
  - Empty expect() edge case
  - Await in expect argument position
  - Async reference function call
  - .not matcher variant
- Improve inline comments for clarity:
  - Document function return value semantics
  - Clarify AST representation detail for concise arrow body
  - Add comment explaining .to_string() lifetime requirement
…on tests

Add `.with_vitest_plugin(true)` to ensure the rule is correctly
detected in test environment. Without this, the vitest framework
context is not enabled and the rule's `run_on_jest_node` method
is never called, causing test cases to incorrectly pass.
@amondnet amondnet force-pushed the feat/vitest-no-unneeded-async-expect-function branch from 94f65f0 to 1e56dad Compare January 6, 2026 10:14
Copy link
Contributor

@camc314 camc314 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!

@camc314 camc314 merged commit efa029f into oxc-project:main Jan 6, 2026
19 checks passed
Dunqing pushed a commit that referenced this pull request Jan 12, 2026
# Oxlint
### 🚀 Features

- 9699a1b linter/prefer-global-this: Add suggestion (#17873) (Mikhail
Baev)
- 51c2815 linter/no-invalid-regexp: Add labels and help text to flag
diagnostics (#17865) (camchenry)
- 344d77d linter/no-ex-assign: Improve diagnostic with more detail
(#17864) (camchenry)
- 7d280e0 linter: Add fixer for
`unicorn/no-useless-error-capture-stack-trace` rule (#17839) (Mikhail
Baev)
- af1d0e3 linter/prefer-optional-chain: Add rule (#17831) (camc314)
- e3c4108 vscode: Add more supported languages to extension (#17812)
(Alexander Lichter)
- 4a46678 vscode: Activate extension on more languages (#17717) (Sysix)
- b1298fc vscode: Sync formatter with supported files (#17615)
(Alexander Lichter)
- c7f0848 linte/rno-required-prop-with-default: Implement suggestion
(#17747) (Minsu Lee)
- 0e8127e linter/vue: Implement no-lifecycle-after-await (#17701)
(yefan)
- 3567304 linter/vitest: Implement `consistent-each-for` (#17601) (Said
Atrahouch)
- 883e156 linter: Add fixer for `unicorn/no-useless-collection-argument`
rule (#17594) (Mikhail Baev)
- 4eb335c linter/vitest: Implemented prefer-called-once (#17674) (Said
Atrahouch)
- 2bd2d5a linter/vitest: Implement hoisted-apis-on-top (#17658) (Said
Atrahouch)
- cfb2bcc linter/vue: Implement no-arrow-functions-in-watch (#17672)
(yefan)
- a68208a linter/eslint-plugin-vitest: Implements
`prefer-describe-function-title` (#17677) (Said Atrahouch)
- efa029f linter/vitest: Implement no-unneeded-async-expect-function
(#17494) (Minsu Lee)

### 🐛 Bug Fixes

- 49cf66e lsp: Fix workspace worker selection for nested and
similar-named workspaces (#17853) (Copilot)
- 84f4f3c linter: Add doc url for tsgolint diagnostics (#17879) (Sysix)
- 76c903f linter/consistent-indexed-object-style: Skip fixing default
exported interface (#17874) (Copilot)
- 7e87d16 linter/tabindex-no-positive: Improve diagnostic phrasing
(#17849) (connorshea)
- 28f9fba vscode: Fix nested search for binaries (#17832) (Sysix)
- 8ca2cd2 linter: Move jsx-a11y/no-static-element-interactions rule to
nursery. (#17818) (connorshea)
- dc9fdd6 linter/consistent-indexed-object-style: Re-port test cases and
fix some bugs (#17802) (camc314)
- 7bbd880 linter: Update prefer-destructuring rule metadata (#17642)
(Hamir Mahal)
- 3c45185 linter/consistent-indexed-object-style: False positive with
circular reference (#17789) (heygsc)
- bd186b4 vscode: Search for `oxlint` and `oxfmt` in every workspace
directory (#17760) (Sysix)
- 3e0dff7 linter/no-hooks: Add punctuation to diagnostic message
(#17751) (camc314)
- 6ae21f9 linter/prefer-called-once: Avoid panic on trailing comma
(#17735) (Said Atrahouch)
- 32c3901 oxlint: Do not panic on invalid `no-unused-vars` configuration
(#17719) (Sysix)
- 59a6228 parser: Detect TS1363 error for type-only imports with mixed
default and named/namespace bindings (#17712) (Copilot)

### ⚡ Performance

- f87a1e2 linter: Check for giving reserved plugin name before calling
`load_plugin` on napi side (#17841) (Sysix)

### 📚 Documentation

- a2b3a24 linter/no-caller: Improve docs and diagnostic for rule.
(#17890) (connorshea)
- aa48247 linter/no-unsafe-finally: Improve rule docs. (#17891)
(connorshea)
- 1b0bdee linter: Tweak docs for no-useless-constructor and
hoisted-apis-on-top (#17888) (connorshea)
- 8f24fa9 vscode: Remove mention of a built-in server (#17836) (Sysix)
- e81a306 linter: Update the tsconfig flag mention for the import
plugin. (#17778) (connorshea)
# Oxfmt
### 🚀 Features

- 539b350 formatter/sort_imports: Update `NODE_BUILTINS` modules
(#17771) (nilptr)
- 2e03ebf oxfmt/lsp: Use `SourceFormatter` to support non-JS files and
napi features (#17655) (leaysgur)
- 623f7eb oxfmt/sort_package_json: Use `options.sort_scripts` (#17740)
(leaysgur)
- 86c0168 oxfmt/sort_package_json: Handle `oxfmtrc.sort_scripts` option
(#17738) (leaysgur)
- 256636a oxfmt/lsp: Add `.editorconfig` to `get_watcher_patterns`
(#17694) (leaysgur)
- 3f3db39 oxfmt/lsp: Use `ConfigResolver` to align with CLI (#17654)
(leaysgur)

### 🐛 Bug Fixes

- fdd1e1e formatter: Don't wrap parenthesis for type assertion when it's
an declaration of export default (#17878) (Dunqing)
- f0813ad formatter: Incorrect type annotation check for short argument
(#17877) (Dunqing)
- 9e89389 formatter/tailwindcss: Nested class string doesn't respect
`singleQuote: true` (#17838) (Dunqing)
- e2f534c formatter/sort_imports: Handle alignable comment with JsLabels
(#17791) (leaysgur)
- f0cedd4 formatter/tailwindcss: Class name is broken after sorting when
its contains single quotes with `singleQuote: true` (#17790) (Dunqing)
- 1864142 oxfmt/tailwindcss: Bundle `prettier/plugins/*` (#17782)
(leaysgur)
- 3a9d43b oxfmt: Ignore explicit positional path which is ignored by
directory (#17732) (leaysgur)
- 0563217 formatter: Classes will be stripped out when both
`experimentalTailwindcss` and `experimentalSortImports` are enabled
(#17726) (Dunqing)

### ⚡ Performance

- d1bc514 formatter: Optimize RegExpLiteral formatting to avoid heap
allocations (#17797) (Dunqing)

### 📚 Documentation

- 62b7a01 formatter: Clarify `experimentalTailwindcss` configuration
comments (#17898) (Dunqing)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-linter Area - Linter C-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants