From 93c419363babea2ebff46ee5b40d127c13966989 Mon Sep 17 00:00:00 2001 From: "Arend van Beelen jr." Date: Wed, 19 Nov 2025 16:34:53 +0100 Subject: [PATCH] fix: correctly place await after comment --- .changeset/balanced-bananas-balloon.md | 5 +++ .../src/lint/nursery/no_floating_promises.rs | 4 +-- .../nursery/noFloatingPromises/invalid.js | 5 +++ .../noFloatingPromises/invalid.js.snap | 27 ++++++++++++++++ crates/biome_rowan/src/ast/batch.rs | 31 +++++++++++++++++++ crates/biome_rowan/src/ast/mod.rs | 9 ++++++ 6 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 .changeset/balanced-bananas-balloon.md diff --git a/.changeset/balanced-bananas-balloon.md b/.changeset/balanced-bananas-balloon.md new file mode 100644 index 000000000000..37c12e9d060e --- /dev/null +++ b/.changeset/balanced-bananas-balloon.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#7999](https://github.com/biomejs/biome/issues/7999): Correctly place `await` after leading comment in auto-fix action from `noFloatingPromises` rule. diff --git a/crates/biome_js_analyze/src/lint/nursery/no_floating_promises.rs b/crates/biome_js_analyze/src/lint/nursery/no_floating_promises.rs index 92565c9a6efb..47762a04f522 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_floating_promises.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_floating_promises.rs @@ -292,10 +292,10 @@ impl Rule for NoFloatingPromises { AnyJsExpression::JsAwaitExpression(make::js_await_expression( make::token(JsSyntaxKind::AWAIT_KW) .with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), - expression.clone().trim_leading_trivia()?, + expression.clone().trim_comments_and_trivia()?, )); - mutation.replace_node(expression, await_expression); + mutation.replace_node_transfer_trivia(expression, await_expression); Some(JsRuleAction::new( ctx.metadata().action_category(ctx.category(), ctx.group()), ctx.metadata().applicability(), diff --git a/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js index 36812f53c7a5..4ffa6f716123 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js @@ -3,3 +3,8 @@ async function returnsPromise() { } returnsPromise(); returnsPromise().then(() => { }).finally(() => { }); + +async function issue7999() { + // ✅ + returnsPromise(); +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js.snap index 891bf156605e..ef69f8c47f97 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.js.snap @@ -10,6 +10,11 @@ async function returnsPromise() { returnsPromise(); returnsPromise().then(() => { }).finally(() => { }); +async function issue7999() { + // ✅ + returnsPromise(); +} + ``` # Diagnostics @@ -40,8 +45,30 @@ invalid.js:5:1 lint/nursery/noFloatingPromises ━━━━━━━━━━━ > 5 │ returnsPromise().then(() => { }).finally(() => { }); │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 │ + 7 │ async function issue7999() { i This happens when a Promise is not awaited, lacks a `.catch` or `.then` rejection handler, or is not explicitly ignored using the `void` operator. ``` + +``` +invalid.js:9:3 lint/nursery/noFloatingPromises FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i A "floating" Promise was found, meaning it is not properly handled and could lead to ignored errors or unexpected behavior. + + 7 │ async function issue7999() { + 8 │ // ✅ + > 9 │ returnsPromise(); + │ ^^^^^^^^^^^^^^^^^ + 10 │ } + 11 │ + + i This happens when a Promise is not awaited, lacks a `.catch` or `.then` rejection handler, or is not explicitly ignored using the `void` operator. + + i Unsafe fix: Add await operator. + + 9 │ ··await·returnsPromise(); + │ ++++++ + +``` diff --git a/crates/biome_rowan/src/ast/batch.rs b/crates/biome_rowan/src/ast/batch.rs index fd28078701ca..d054da1ca3ce 100644 --- a/crates/biome_rowan/src/ast/batch.rs +++ b/crates/biome_rowan/src/ast/batch.rs @@ -221,6 +221,37 @@ where self.replace_element_discard_trivia(prev_token.into(), next_token.into()) } + /// Push a change to replace the "prev_node" with "next_node". + /// + /// - leading trivia of `prev_node` + /// - leading trivia of `next_node` + /// - trailing trivia of `prev_node` + /// - trailing trivia of `next_node` + pub fn replace_node_transfer_trivia(&mut self, prev_node: T, next_node: T) -> Option<()> + where + T: AstNode, + { + let prev_node = prev_node.into_syntax(); + let next_node = next_node.into_syntax(); + + let leading_trivia = chain_trivia_pieces( + prev_node.first_token()?.leading_trivia().pieces(), + next_node.first_token()?.leading_trivia().pieces(), + ); + + let trailing_trivia = chain_trivia_pieces( + prev_node.last_token()?.trailing_trivia().pieces(), + next_node.last_token()?.trailing_trivia().pieces(), + ); + let new_node = next_node + .with_leading_trivia_pieces(leading_trivia)? + .with_trailing_trivia_pieces(trailing_trivia)?; + + self.replace_element_discard_trivia(prev_node.into(), new_node.into()); + + Some(()) + } + /// Push a change to replace the "prev_token" with "next_token". /// /// - leading trivia of `prev_token` diff --git a/crates/biome_rowan/src/ast/mod.rs b/crates/biome_rowan/src/ast/mod.rs index cedb0b95d15b..6d223125c3f4 100644 --- a/crates/biome_rowan/src/ast/mod.rs +++ b/crates/biome_rowan/src/ast/mod.rs @@ -276,6 +276,15 @@ pub trait AstNode: Clone { Self::cast(self.into_syntax().append_trivia_pieces(trivia)?) } + /// Returns a new version of this node with *all* trivia stripped. + fn trim_comments_and_trivia(self) -> Option { + Self::cast( + self.into_syntax() + .with_leading_trivia_pieces([])? + .with_trailing_trivia_pieces([])?, + ) + } + /// Return a new version of this node without leading and trailing newlines and whitespaces. fn trim_trivia(self) -> Option { Self::cast(