diff --git a/.changeset/use-await-yield-star.md b/.changeset/use-await-yield-star.md new file mode 100644 index 000000000000..d80837cb586c --- /dev/null +++ b/.changeset/use-await-yield-star.md @@ -0,0 +1,6 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8645](https://github.com/biomejs/biome/issues/8645). +[useAwait](https://biomejs.dev/linter/rules/use-await/) no longer reports `async` generator functions that use `yield*`, since `yield*` in an async generator delegates to an `AsyncIterable` and requires the `async` modifier. diff --git a/crates/biome_js_analyze/src/lint/suspicious/use_await.rs b/crates/biome_js_analyze/src/lint/suspicious/use_await.rs index 9d8db8eda461..d0fd8fe65913 100644 --- a/crates/biome_js_analyze/src/lint/suspicious/use_await.rs +++ b/crates/biome_js_analyze/src/lint/suspicious/use_await.rs @@ -5,7 +5,8 @@ use biome_analyze::{ use biome_console::markup; use biome_diagnostics::Severity; use biome_js_syntax::{ - AnyFunctionLike, JsAwaitExpression, JsForOfStatement, JsLanguage, TextRange, WalkEvent, + AnyFunctionLike, JsAwaitExpression, JsForOfStatement, JsLanguage, JsYieldExpression, + TextRange, WalkEvent, }; use biome_rowan::{AstNode, AstNodeList, Language, SyntaxNode, TextSize}; use biome_rule_options::use_await::UseAwaitOptions; @@ -46,6 +47,11 @@ declare_lint_rule! { /// /// // Nor does it warn about empty `async` functions /// async function noop() { } + /// + /// // Async generators that use `yield*` with an async iterable + /// async function* delegateToAsyncIterable() { + /// yield* otherAsyncIterable(); + /// } /// ``` pub UseAwait { version: "1.4.0", @@ -87,6 +93,11 @@ impl Visitor for MissingAwaitVisitor { *has_await = true; } else if let Some(for_of) = JsForOfStatement::cast_ref(node) { *has_await = *has_await || for_of.await_token().is_some(); + } else if let Some(yield_expr) = JsYieldExpression::cast_ref(node) { + *has_await = *has_await + || yield_expr + .argument() + .is_some_and(|arg| arg.star_token().is_some()); } } } diff --git a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js index 32b50828b979..0a96f57401f6 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js @@ -88,3 +88,8 @@ class Sample { return true; } } + +async function* yieldWithoutStar() { + yield 1; + yield 2; +} diff --git a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js.snap index b051d7299ca1..7412766c9ee1 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/invalid.js.snap @@ -95,6 +95,11 @@ class Sample { } } +async function* yieldWithoutStar() { + yield 1; + yield 2; +} + ``` # Diagnostics @@ -589,3 +594,35 @@ invalid.js:86:2 lint/suspicious/useAwait ━━━━━━━━━━━━━ ``` + +``` +invalid.js:92:1 lint/suspicious/useAwait ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This async function lacks an await expression. + + 90 │ } + 91 │ + > 92 │ async function* yieldWithoutStar() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 93 │ yield 1; + > 94 │ yield 2; + > 95 │ } + │ ^ + 96 │ + + i Remove this async modifier, or add an await expression in the function. + + 90 │ } + 91 │ + > 92 │ async function* yieldWithoutStar() { + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + > 93 │ yield 1; + > 94 │ yield 2; + > 95 │ } + │ ^ + 96 │ + + i Async functions without await expressions may not need to be declared async. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js index 9755ce32fc56..3f772187236c 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js +++ b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js @@ -67,3 +67,12 @@ async function awaitExpressionWithForOf () { return sum; }; +async function* yieldStarAsyncIterable() { + yield* otherAsyncGenerator(); +} + +async function* yieldStarWithAwait() { + const data = await fetch('/data'); + yield* processData(data); +} + diff --git a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js.snap b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js.snap index 273df19ee143..abbaddcb9040 100644 --- a/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/suspicious/useAwait/valid.js.snap @@ -73,5 +73,14 @@ async function awaitExpressionWithForOf () { return sum; }; +async function* yieldStarAsyncIterable() { + yield* otherAsyncGenerator(); +} + +async function* yieldStarWithAwait() { + const data = await fetch('/data'); + yield* processData(data); +} + ```