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 04274f9a63bbf..e53a1dc565382 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs @@ -2336,13 +2336,7 @@ impl<'a> AstNode<'a, AssignmentTargetPattern<'a>> { impl<'a> AstNode<'a, ArrayAssignmentTarget<'a>> { #[inline] pub fn elements(&self) -> &AstNode<'a, Vec<'a, Option>>> { - 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.elements, allocator: self.allocator, @@ -2353,7 +2347,7 @@ impl<'a> AstNode<'a, ArrayAssignmentTarget<'a>> { #[inline] pub fn rest(&self) -> Option<&AstNode<'a, AssignmentTargetRest<'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(), @@ -2377,13 +2371,7 @@ impl<'a> AstNode<'a, ArrayAssignmentTarget<'a>> { impl<'a> AstNode<'a, ObjectAssignmentTarget<'a>> { #[inline] pub fn properties(&self) -> &AstNode<'a, Vec<'a, AssignmentTargetProperty<'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.properties, allocator: self.allocator, @@ -2394,7 +2382,7 @@ impl<'a> AstNode<'a, ObjectAssignmentTarget<'a>> { #[inline] pub fn rest(&self) -> Option<&AstNode<'a, AssignmentTargetRest<'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(), @@ -3979,13 +3967,7 @@ impl<'a> AstNode<'a, AssignmentPattern<'a>> { impl<'a> AstNode<'a, ObjectPattern<'a>> { #[inline] pub fn properties(&self) -> &AstNode<'a, Vec<'a, BindingProperty<'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.properties, allocator: self.allocator, @@ -3996,7 +3978,7 @@ impl<'a> AstNode<'a, ObjectPattern<'a>> { #[inline] pub fn rest(&self) -> Option<&AstNode<'a, BindingRestElement<'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(), @@ -4063,13 +4045,7 @@ impl<'a> AstNode<'a, BindingProperty<'a>> { impl<'a> AstNode<'a, ArrayPattern<'a>> { #[inline] pub fn elements(&self) -> &AstNode<'a, Vec<'a, Option>>> { - 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.elements, allocator: self.allocator, @@ -4080,7 +4056,7 @@ impl<'a> AstNode<'a, ArrayPattern<'a>> { #[inline] pub fn rest(&self) -> Option<&AstNode<'a, BindingRestElement<'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 f381ba9ba297e..779b39d747901 100644 --- a/crates/oxc_formatter/src/ast_nodes/iterator.rs +++ b/crates/oxc_formatter/src/ast_nodes/iterator.rs @@ -161,18 +161,33 @@ macro_rules! impl_ast_node_vec { macro_rules! impl_ast_node_vec_for_option { ($type:ty) => { + impl_ast_node_vec_for_option!($type, false); + }; + ($type:ty, has_following_span_in_the_last_item) => { + impl_ast_node_vec_for_option!($type, true); + }; + ($type:ty, $has_following_span_in_the_last_item:tt) => { impl<'a> AstNode<'a, Vec<'a, $type>> { pub fn iter(&self) -> AstNodeIterator<'a, $type> { AstNodeIterator { inner: self.inner.iter().peekable(), parent: self.parent, allocator: self.allocator, - following_span_start: 0, + following_span_start: if $has_following_span_in_the_last_item { + self.following_span_start + } else { + 0 + }, get_following_span_start: |opt| opt.as_ref().map_or(0, |n| n.span().start), } } pub fn first(&self) -> Option<&'a AstNode<'a, $type>> { + let following = if $has_following_span_in_the_last_item { + self.following_span_start + } else { + 0 + }; let mut inner_iter = self.inner.iter(); self.allocator .alloc(inner_iter.next().map(|inner| { @@ -183,19 +198,24 @@ macro_rules! impl_ast_node_vec_for_option { following_span_start: inner_iter .next() .and_then(|opt| opt.as_ref().map(|n| n.span().start)) - .unwrap_or(0), + .unwrap_or(following), } })) .as_ref() } pub fn last(&self) -> Option<&'a AstNode<'a, $type>> { + let following = if $has_following_span_in_the_last_item { + self.following_span_start + } else { + 0 + }; self.allocator .alloc(self.inner.last().map(|inner| AstNode { inner, parent: self.parent, allocator: self.allocator, - following_span_start: 0, + following_span_start: following, })) .as_ref() } @@ -226,7 +246,11 @@ macro_rules! impl_ast_node_vec_for_option { inner: self.inner.iter().peekable(), parent: self.parent, allocator: self.allocator, - following_span_start: 0, + following_span_start: if $has_following_span_in_the_last_item { + self.following_span_start + } else { + 0 + }, get_following_span_start: |opt| opt.as_ref().map_or(0, |n| n.span().start), } } @@ -239,10 +263,8 @@ impl_ast_node_vec!(ArrayExpressionElement<'a>); impl_ast_node_vec!(ObjectPropertyKind<'a>); impl_ast_node_vec!(TemplateElement<'a>); impl_ast_node_vec!(Argument<'a>); -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!(ClassElement<'a>); impl_ast_node_vec!(ImportDeclarationSpecifier<'a>); impl_ast_node_vec!(ImportAttribute<'a>); @@ -258,15 +280,19 @@ impl_ast_node_vec!(TSSignature<'a>); impl_ast_node_vec!(TSIndexSignatureName<'a>); impl_ast_node_vec!(TSInterfaceHeritage<'a>); impl_ast_node_vec!(Decorator<'a>); -impl_ast_node_vec_for_option!(Option>); -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`). +// These types need `following_span_start` to correctly attribute comments between +// the last item and the rest element (e.g., `[a, /** @type {string[]} */ ...rest]`). impl_ast_node_vec!(FormalParameter<'a>, has_following_span_in_the_last_item); +impl_ast_node_vec!(BindingProperty<'a>, has_following_span_in_the_last_item); +impl_ast_node_vec!(AssignmentTargetProperty<'a>, has_following_span_in_the_last_item); +impl_ast_node_vec_for_option!(Option>, has_following_span_in_the_last_item); +impl_ast_node_vec_for_option!( + Option>, + has_following_span_in_the_last_item +); // Custom get_span for Statement to handle decorated exports. // diff --git a/crates/oxc_formatter/tests/fixtures/js/typecast/rest_element_comments.js b/crates/oxc_formatter/tests/fixtures/js/typecast/rest_element_comments.js new file mode 100644 index 0000000000000..83f267ecc6b51 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/js/typecast/rest_element_comments.js @@ -0,0 +1,16 @@ +// Type cast comments on rest elements should stay after the comma + +// ArrayPattern +const [a, /** @type {string[]} */ ...rest1] = arr; + +// ObjectPattern +const { a: x, /** @type {object} */ ...rest2 } = obj; + +// ArrayAssignmentTarget +[a, /** @type {string[]} */ ...rest3] = arr; + +// ObjectAssignmentTarget +({ a: x, /** @type {object} */ ...rest4 } = obj); + +// Nested patterns +const [{ a, /** @type {number} */ ...inner }, /** @type {any[]} */ ...outer] = nested; diff --git a/crates/oxc_formatter/tests/fixtures/js/typecast/rest_element_comments.js.snap b/crates/oxc_formatter/tests/fixtures/js/typecast/rest_element_comments.js.snap new file mode 100644 index 0000000000000..42b63a623a58d --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/js/typecast/rest_element_comments.js.snap @@ -0,0 +1,64 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +// Type cast comments on rest elements should stay after the comma + +// ArrayPattern +const [a, /** @type {string[]} */ ...rest1] = arr; + +// ObjectPattern +const { a: x, /** @type {object} */ ...rest2 } = obj; + +// ArrayAssignmentTarget +[a, /** @type {string[]} */ ...rest3] = arr; + +// ObjectAssignmentTarget +({ a: x, /** @type {object} */ ...rest4 } = obj); + +// Nested patterns +const [{ a, /** @type {number} */ ...inner }, /** @type {any[]} */ ...outer] = nested; + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +// Type cast comments on rest elements should stay after the comma + +// ArrayPattern +const [a, /** @type {string[]} */ ...rest1] = arr; + +// ObjectPattern +const { a: x, /** @type {object} */ ...rest2 } = obj; + +// ArrayAssignmentTarget +[a, /** @type {string[]} */ ...rest3] = arr; + +// ObjectAssignmentTarget +({ a: x, /** @type {object} */ ...rest4 } = obj); + +// Nested patterns +const [{ a, /** @type {number} */ ...inner }, /** @type {any[]} */ ...outer] = + nested; + +------------------- +{ printWidth: 100 } +------------------- +// Type cast comments on rest elements should stay after the comma + +// ArrayPattern +const [a, /** @type {string[]} */ ...rest1] = arr; + +// ObjectPattern +const { a: x, /** @type {object} */ ...rest2 } = obj; + +// ArrayAssignmentTarget +[a, /** @type {string[]} */ ...rest3] = arr; + +// ObjectAssignmentTarget +({ a: x, /** @type {object} */ ...rest4 } = obj); + +// Nested patterns +const [{ a, /** @type {number} */ ...inner }, /** @type {any[]} */ ...outer] = nested; + +===================== End ===================== diff --git a/tasks/ast_tools/src/generators/formatter/ast_nodes.rs b/tasks/ast_tools/src/generators/formatter/ast_nodes.rs index f61352e62d45b..198a0a87700df 100644 --- a/tasks/ast_tools/src/generators/formatter/ast_nodes.rs +++ b/tasks/ast_tools/src/generators/formatter/ast_nodes.rs @@ -25,7 +25,13 @@ pub fn get_node_type(ty: &TokenStream) -> TokenStream { /// /// 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_WITHOUT_FOLLOWING_NODE_LIST: &[&str] = &[ + "FormalParameters", + "ArrayPattern", + "ObjectPattern", + "ArrayAssignmentTarget", + "ObjectAssignmentTarget", +]; const AST_NODE_WITH_FOLLOWING_NODE_LIST: &[&str] = &["Function", "Class"];