diff --git a/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs b/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs index 5de415e3b7c95..04274f9a63bbf 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs @@ -4274,13 +4274,7 @@ impl<'a> AstNode<'a, FormalParameters<'a>> { #[inline] pub fn items(&self) -> &AstNode<'a, Vec<'a, FormalParameter<'a>>> { - let following_span_start = self - .inner - .rest - .as_deref() - .map(|n| n.span().start) - .or(Some(self.following_span_start)) - .unwrap_or(0); + let following_span_start = self.inner.rest.as_deref().map_or(0, |n| n.span().start); self.allocator.alloc(AstNode { inner: &self.inner.items, allocator: self.allocator, @@ -4291,7 +4285,7 @@ impl<'a> AstNode<'a, FormalParameters<'a>> { #[inline] pub fn rest(&self) -> Option<&AstNode<'a, FormalParameterRest<'a>>> { - let following_span_start = self.following_span_start; + let following_span_start = 0; self.allocator .alloc(self.inner.rest.as_ref().map(|inner| AstNode { inner: inner.as_ref(), diff --git a/crates/oxc_formatter/src/ast_nodes/iterator.rs b/crates/oxc_formatter/src/ast_nodes/iterator.rs index f04be3d5a4cd5..f381ba9ba297e 100644 --- a/crates/oxc_formatter/src/ast_nodes/iterator.rs +++ b/crates/oxc_formatter/src/ast_nodes/iterator.rs @@ -243,7 +243,6 @@ impl_ast_node_vec!(AssignmentTargetProperty<'a>); impl_ast_node_vec!(VariableDeclarator<'a>); impl_ast_node_vec!(SwitchCase<'a>); impl_ast_node_vec!(BindingProperty<'a>); -impl_ast_node_vec!(FormalParameter<'a>); impl_ast_node_vec!(ClassElement<'a>); impl_ast_node_vec!(ImportDeclarationSpecifier<'a>); impl_ast_node_vec!(ImportAttribute<'a>); @@ -265,6 +264,10 @@ impl_ast_node_vec_for_option!(Option>); // Directive needs `following_span_start` to distinguish trailing comments from leading comments // of the first statement. See the struct field comment for `following_span_start` for details. impl_ast_node_vec!(Directive<'a>, has_following_span_in_the_last_item); +// FormalParameter needs `following_span_start` to correctly attribute comments between +// the last parameter and the rest parameter (e.g., `param, /** @type {string[]} */ ...rest`). +impl_ast_node_vec!(FormalParameter<'a>, has_following_span_in_the_last_item); + // Custom get_span for Statement to handle decorated exports. // impl_ast_node_vec!(Statement<'a>, false, get_statement_span); diff --git a/crates/oxc_formatter/tests/fixtures/js/ignore/issue-18589.js b/crates/oxc_formatter/tests/fixtures/js/ignore/issue-18589.js new file mode 100644 index 0000000000000..fe09eb930c91e --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/js/ignore/issue-18589.js @@ -0,0 +1,2 @@ +// type cast on rest parameter should stay after comma +(/** @type {string} */ param, /** @type {string[]} */ ...rest) => {}; diff --git a/crates/oxc_formatter/tests/fixtures/js/ignore/issue-18589.js.snap b/crates/oxc_formatter/tests/fixtures/js/ignore/issue-18589.js.snap new file mode 100644 index 0000000000000..4dd935c98ff3a --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/js/ignore/issue-18589.js.snap @@ -0,0 +1,21 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +// type cast on rest parameter should stay after comma +(/** @type {string} */ param, /** @type {string[]} */ ...rest) => {}; + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +// type cast on rest parameter should stay after comma +(/** @type {string} */ param, /** @type {string[]} */ ...rest) => {}; + +------------------- +{ printWidth: 100 } +------------------- +// type cast on rest parameter should stay after comma +(/** @type {string} */ param, /** @type {string[]} */ ...rest) => {}; + +===================== End ===================== diff --git a/tasks/ast_tools/src/generators/formatter/ast_nodes.rs b/tasks/ast_tools/src/generators/formatter/ast_nodes.rs index 57164fb831fed..f61352e62d45b 100644 --- a/tasks/ast_tools/src/generators/formatter/ast_nodes.rs +++ b/tasks/ast_tools/src/generators/formatter/ast_nodes.rs @@ -21,9 +21,11 @@ pub fn get_node_type(ty: &TokenStream) -> TokenStream { quote! { AstNode<'a, #ty> } } -/// Based on the printing comments algorithm, the last child of these AST nodes don't need to print comments. -/// Without following nodes could lead to only print comments that before the end of the node, which is what we want. -const AST_NODE_WITHOUT_FOLLOWING_NODE_LIST: &[&str] = &[]; +/// AST nodes whose last child should have `following_span_start = 0`. +/// +/// This ensures trailing comments are correctly attributed to the last child itself, +/// rather than being treated as leading comments of a following sibling outside the parent. +const AST_NODE_WITHOUT_FOLLOWING_NODE_LIST: &[&str] = &["FormalParameters"]; const AST_NODE_WITH_FOLLOWING_NODE_LIST: &[&str] = &["Function", "Class"];