fix(linter/eslint/require-await): count await using as an await (#27080)

## Root cause

`AwaitFinder`
(`crates/oxc_linter/src/rules/eslint/require_await.rs:220-239`) only
sets `found` for `await` expressions and `for await`. An `await using`
declaration also awaits (the resource's disposal when the scope exits),
so an async function whose only await is `await using x = ...` or `for
(await using x of xs)` is reported.

The rule's dangerous fix then removes `async`, which leaves `await
using` outside an async function. With oxlint 1.85.0:

```js
// input
export async function f() {
  await using x = getResource();
}

// after --fix-dangerously
export function f() {
  await using x = getResource();
}
```

The fixed file no longer parses (`` `await` is only allowed within async
functions and at the top levels of modules ``). In a debug build the
same fix trips the fixer's syntax check
(`crates/oxc_linter/src/fixer/mod.rs:422`).

ESLint counts these declarations since eslint/eslint#19828
([`lib/rules/require-await.js#L180-L182`](https://github.com/eslint/eslint/blob/6fb3685bcbe9a6f72fd7dfb9129686b6fb96b0bd/lib/rules/require-await.js#L180-L182)):

```js
if (node.kind === "await using") {
	scopeInfo.hasAwait = true;
}
```

## Notes

AI-assisted: I used Claude Code while investigating; I reviewed, ran and
verified every change myself.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
This commit is contained in:
authored and GitHub committed 2026-09-28 08:54:39 +01:00
1 parent 97fe8b9f84
commit 2ba7e33f2c
2 files changed
+32 -2

No files matched your search

@@ -2,10 +2,13 @@ use oxc_ast::{
AstKind,
ast::{
ArrowFunctionExpression, AwaitExpression, ForOfStatement, Function, FunctionBody,
FunctionType, MethodDefinition, ObjectProperty, PropertyKey,
FunctionType, MethodDefinition, ObjectProperty, PropertyKey, VariableDeclaration,
},
};
use oxc_ast_visit::{VisitJs, walk_js::walk_for_of_statement};
use oxc_ast_visit::{
VisitJs,
walk_js::{walk_for_of_statement, walk_variable_declaration},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ScopeFlags;
@@ -233,6 +236,14 @@ impl<'a> VisitJs<'a> for AwaitFinder {
}
}
fn visit_variable_declaration(&mut self, decl: &VariableDeclaration<'a>) {
if decl.kind.is_await() {
self.found = true;
} else {
walk_variable_declaration(self, decl);
}
}
fn visit_arrow_function_expression(&mut self, _expr: &ArrowFunctionExpression<'a>) {}
fn visit_function(&mut self, _func: &Function<'a>, _flags: ScopeFlags) {}
@@ -287,6 +298,9 @@ fn test() {
}
};
",
"async function foo() { await using x = getResource() }",
"async function foo() { { await using x = getResource() } }",
"async function foo() { for (await using x of xs) {} }",
];
let fail = vec![
@@ -300,6 +314,8 @@ fn test() {
"(class { async ''() { doSomething() } })",
"async function foo() { async () => { await doSomething() } }",
"async function foo() { await (async () => { doSomething() }) }",
"async function foo() { using x = getResource() }",
"async function foo() { async () => { await using x = getResource() } }",
];
let fix = vec![
@@ -71,3 +71,17 @@ source: crates/oxc_linter/src/tester.rs
· ─────────────────────────────
╰────
help: Consider removing the `async` keyword.
⚠ eslint(require-await): Async function has no `await` expression.
╭─[require_await.mts:1:16]
1 │ async function foo() { using x = getResource() }
· ───
╰────
help: Consider removing the `async` keyword.
⚠ eslint(require-await): Async function has no `await` expression.
╭─[require_await.mts:1:16]
1 │ async function foo() { async () => { await using x = getResource() } }
· ───
╰────
help: Consider removing the `async` keyword.