diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 17b20eb979e5d..6b35c5672a997 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -220,8 +220,11 @@ impl RuleRunner for crate::rules::eslint::no_async_promise_executor::NoAsyncProm } impl RuleRunner for crate::rules::eslint::no_await_in_loop::NoAwaitInLoop { - const NODE_TYPES: Option<&AstTypesBitset> = - Some(&AstTypesBitset::from_types(&[AstType::AwaitExpression, AstType::ForOfStatement])); + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::AwaitExpression, + AstType::ForOfStatement, + AstType::VariableDeclaration, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } diff --git a/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs b/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs index 19f369cbaa604..43d2c6b9e7ac8 100644 --- a/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs +++ b/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs @@ -1,10 +1,10 @@ use oxc_ast::{ AstKind, - ast::{Expression, Statement}, + ast::{Expression, Statement, VariableDeclarationKind}, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use crate::{AstNode, context::LintContext, rule::Rule}; @@ -49,7 +49,7 @@ declare_oxc_lint!( impl Rule for NoAwaitInLoop { fn run(&self, node: &AstNode, ctx: &LintContext) { - // if node is AwaitExpression or AwaitForOfStatement + // if node is AwaitExpression, AwaitForOfStatement, or VariableDeclaration with `await using` let span = match node.kind() { // if the await attr of ForOfStatement is false, return AstKind::ForOfStatement(for_of_stmt) => { @@ -62,12 +62,21 @@ impl Rule for NoAwaitInLoop { } // only highlight the 'await' keyword AstKind::AwaitExpression(expr) => Span::sized(expr.span.start, 5), + // handle `await using` declarations + AstKind::VariableDeclaration(decl) => { + if decl.kind != VariableDeclarationKind::AwaitUsing { + return; + } + // only highlight the 'await' keyword + Span::sized(decl.span.start, 5) + } // other node type, return _ => return, }; let nodes = ctx.nodes(); - // Perform validation for AwaitExpression and ForOfStatement that contains await + // Perform validation for AwaitExpression, ForOfStatement that contains await, and await using + let mut current_node = node; let mut parent_node = nodes.parent_node(node.id()); let mut is_in_loop = false; while !matches!(parent_node.kind(), AstKind::Program(_)) { @@ -76,12 +85,13 @@ impl Rule for NoAwaitInLoop { break; } - // if AwaitExpression or AwaitForOfStatement are in loop, break and report error - if Self::is_looped(span, parent_node) { + // if AwaitExpression, AwaitForOfStatement, or await using are in loop, break and report error + if Self::is_looped(current_node, parent_node) { is_in_loop = true; break; } + current_node = parent_node; parent_node = nodes.parent_node(parent_node.id()); } @@ -131,7 +141,8 @@ impl NoAwaitInLoop { } } - fn is_looped(span: Span, parent: &AstNode) -> bool { + fn is_looped(node: &AstNode, parent: &AstNode) -> bool { + let span = node.kind().span(); match parent.kind() { AstKind::ForStatement(stmt) => { let mut result = Self::node_matches_stmt_span(span, &stmt.body); @@ -152,8 +163,32 @@ impl NoAwaitInLoop { result } - AstKind::ForInStatement(stmt) => Self::node_matches_stmt_span(span, &stmt.body), - AstKind::ForOfStatement(stmt) => Self::node_matches_stmt_span(span, &stmt.body), + AstKind::ForInStatement(stmt) => { + if Self::node_matches_stmt_span(span, &stmt.body) { + return true; + } + // Check if this is `await using` in the left position + if let AstKind::VariableDeclaration(decl) = node.kind() + && decl.kind == VariableDeclarationKind::AwaitUsing + && stmt.left.span().contains_inclusive(span) + { + return true; + } + false + } + AstKind::ForOfStatement(stmt) => { + if Self::node_matches_stmt_span(span, &stmt.body) { + return true; + } + // Check if this is `await using` in the left position + if let AstKind::VariableDeclaration(decl) = node.kind() + && decl.kind == VariableDeclarationKind::AwaitUsing + && stmt.left.span().contains_inclusive(span) + { + return true; + } + false + } AstKind::WhileStatement(stmt) => { Self::node_matches_stmt_span(span, &stmt.body) || Self::node_matches_expr_span(span, &stmt.test) @@ -233,10 +268,10 @@ fn test() { "async function foo() { while (xyz || 5 > await x) { } }", // In a nested loop of for-await-of "async function foo() { for await (var x of xs) { while (1) await f(x) } }", - // TODO: Get these working. - // "while (true) { await using resource = getResource(); }", // { "sourceType": "module", "ecmaVersion": 2026 } - // "for (;;) { await using resource = getResource(); }", // { "sourceType": "module", "ecmaVersion": 2026 } - // "for (await using resource of resources) {}", // { "sourceType": "module", "ecmaVersion": 2026 } + // `await using` declarations + "while (true) { await using resource = getResource(); }", // { "sourceType": "module", "ecmaVersion": 2026 } + "for (;;) { await using resource = getResource(); }", // { "sourceType": "module", "ecmaVersion": 2026 } + "for (await using resource of resources) {}", // { "sourceType": "module", "ecmaVersion": 2026 } ]; Tester::new(NoAwaitInLoop::NAME, NoAwaitInLoop::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/eslint_no_await_in_loop.snap b/crates/oxc_linter/src/snapshots/eslint_no_await_in_loop.snap index 5f683e38a8e59..940332bd00b40 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_await_in_loop.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_await_in_loop.snap @@ -84,3 +84,21 @@ source: crates/oxc_linter/src/tester.rs 1 │ async function foo() { for await (var x of xs) { while (1) await f(x) } } · ───── ╰──── + + ⚠ eslint(no-await-in-loop): Unexpected `await` inside a loop. + ╭─[no_await_in_loop.tsx:1:16] + 1 │ while (true) { await using resource = getResource(); } + · ───── + ╰──── + + ⚠ eslint(no-await-in-loop): Unexpected `await` inside a loop. + ╭─[no_await_in_loop.tsx:1:12] + 1 │ for (;;) { await using resource = getResource(); } + · ───── + ╰──── + + ⚠ eslint(no-await-in-loop): Unexpected `await` inside a loop. + ╭─[no_await_in_loop.tsx:1:6] + 1 │ for (await using resource of resources) {} + · ───── + ╰────