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 35399664ae11e..fb442d9150605 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/ast_nodes.rs @@ -1396,7 +1396,7 @@ impl<'a> AstNode<'a, ObjectProperty<'a>> { #[inline] pub fn value(&self) -> &AstNode<'a, Expression<'a>> { - let following_span = None; + let following_span = self.following_span; self.allocator.alloc(AstNode { inner: &self.inner.value, allocator: self.allocator, @@ -1623,7 +1623,7 @@ impl<'a> AstNode<'a, ComputedMemberExpression<'a>> { #[inline] pub fn expression(&self) -> &AstNode<'a, Expression<'a>> { - let following_span = None; + let following_span = self.following_span; self.allocator.alloc(AstNode { inner: &self.inner.expression, allocator: self.allocator, @@ -1660,7 +1660,7 @@ impl<'a> AstNode<'a, StaticMemberExpression<'a>> { #[inline] pub fn property(&self) -> &AstNode<'a, IdentifierName<'a>> { - let following_span = None; + let following_span = self.following_span; self.allocator.alloc(AstNode { inner: &self.inner.property, allocator: self.allocator, @@ -2144,7 +2144,7 @@ impl<'a> AstNode<'a, AssignmentExpression<'a>> { #[inline] pub fn right(&self) -> &AstNode<'a, Expression<'a>> { - let following_span = None; + let following_span = self.following_span; self.allocator.alloc(AstNode { inner: &self.inner.right, allocator: self.allocator, @@ -4131,7 +4131,7 @@ impl<'a> AstNode<'a, FormalParameters<'a>> { #[inline] pub fn items(&self) -> &AstNode<'a, Vec<'a, FormalParameter<'a>>> { - let following_span = self.inner.rest.as_deref().map(GetSpan::span); + let following_span = self.inner.rest.as_deref().map(GetSpan::span).or(self.following_span); self.allocator.alloc(AstNode { inner: &self.inner.items, allocator: self.allocator, @@ -4142,7 +4142,7 @@ impl<'a> AstNode<'a, FormalParameters<'a>> { #[inline] pub fn rest(&self) -> Option<&AstNode<'a, BindingRestElement<'a>>> { - let following_span = None; + let following_span = self.following_span; self.allocator .alloc(self.inner.rest.as_ref().map(|inner| AstNode { inner: inner.as_ref(), diff --git a/crates/oxc_formatter/src/formatter/comments.rs b/crates/oxc_formatter/src/formatter/comments.rs index 98b679558bbfe..728e05f2b1ca8 100644 --- a/crates/oxc_formatter/src/formatter/comments.rs +++ b/crates/oxc_formatter/src/formatter/comments.rs @@ -177,6 +177,12 @@ impl<'a> Comments<'a> { &self.unprinted_comments()[..index] } + /// Returns all block comments that end before or at the given position. + pub fn line_comments_before(&self, pos: u32) -> &'a [Comment] { + let index = self.comments_before_iter(pos).take_while(|c| c.is_line()).count(); + &self.unprinted_comments()[..index] + } + /// Returns comments that are on their own line and end before or at the given position. pub fn own_line_comments_before(&self, pos: u32) -> &'a [Comment] { let index = diff --git a/crates/oxc_formatter/src/write/array_pattern.rs b/crates/oxc_formatter/src/write/array_pattern.rs index c8c14a0794936..b08a6c256caad 100644 --- a/crates/oxc_formatter/src/write/array_pattern.rs +++ b/crates/oxc_formatter/src/write/array_pattern.rs @@ -7,10 +7,7 @@ use crate::{ Format, FormatResult, ast_nodes::{AstNode, AstNodes}, formatter::{Formatter, prelude::*, trivia::format_dangling_comments}, - utils::{ - array::write_array_node, - format_node_without_trailing_comments::FormatNodeWithoutTrailingComments, - }, + utils::array::write_array_node, write, }; @@ -26,23 +23,6 @@ impl<'a> Deref for FormatArrayPattern<'a, '_> { } } -impl GetSpan for FormatArrayPattern<'_, '_> { - fn span(&self) -> Span { - // `[a, b]: [a, b]` - // ^^^^^^^^^^^^^^ ArrayPattern's span covers the type annotation if exists, - // ^^^^^^ but we want the span to cover only the pattern itself, otherwise, - // the comments of type annotation will be treated as dangling comments - // of ArrayPattern. - if let AstNodes::FormalParameter(param) = self.parent - && let Some(ty) = ¶m.pattern.type_annotation - { - Span::new(self.span.start, ty.span.start) - } else { - self.span - } - } -} - impl<'a> Format<'a> for FormatArrayPattern<'a, '_> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { write!(f, "[")?; @@ -75,11 +55,6 @@ impl<'a> Format<'a> for FormatArrayPattern<'a, '_> { impl<'a> FormatWrite<'a> for AstNode<'a, ArrayPattern<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if matches!(self.parent, AstNodes::FormalParameter(param) if param.pattern.type_annotation.is_some()) - { - FormatNodeWithoutTrailingComments(&FormatArrayPattern(self)).fmt(f) - } else { - FormatArrayPattern(self).fmt(f) - } + FormatArrayPattern(self).fmt(f) } } diff --git a/crates/oxc_formatter/src/write/export_declarations.rs b/crates/oxc_formatter/src/write/export_declarations.rs index 8116ab69a73fc..88de7e89731d9 100644 --- a/crates/oxc_formatter/src/write/export_declarations.rs +++ b/crates/oxc_formatter/src/write/export_declarations.rs @@ -28,11 +28,10 @@ fn format_export_keyword_with_class_decorators<'a>( ) -> FormatResult<()> { // `@decorator export class Cls {}` // ^ print leading comments here - let format_leading_comments = |f: &mut Formatter<'_, 'a>| -> FormatResult<()> { + let format_leading_comments = format_once(|f| { let comments = f.context().comments().comments_before(span.start); - FormatLeadingComments::Comments(comments).fmt(f)?; - Ok(()) - }; + FormatLeadingComments::Comments(comments).fmt(f) + }); if let AstNodes::Class(class) = declaration && !class.decorators.is_empty() @@ -41,19 +40,26 @@ fn format_export_keyword_with_class_decorators<'a>( // `@decorator export class Cls {}` // decorators are placed before the export keyword if class.decorators[0].span.end < span.start { - write!(f, [class.decorators(), hard_line_break()])?; - format_leading_comments(f)?; - write!(f, [keyword, space()]) + write!( + f, + [class.decorators(), hard_line_break(), format_leading_comments, keyword, space()] + ) } else { // `export @decorator class Cls {}` // decorators are placed after the export keyword - format_leading_comments(f)?; - write!(f, [keyword, hard_line_break()])?; - write!(f, [class.decorators(), hard_line_break()]) + write!( + f, + [ + format_leading_comments, + keyword, + hard_line_break(), + class.decorators(), + hard_line_break() + ] + ) } } else { - format_leading_comments(f)?; - write!(f, [keyword, space()]) + write!(f, [format_leading_comments, keyword, space()]) } } @@ -195,14 +201,8 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, ExportSpecifier<'a>>> { impl<'a> FormatWrite<'a> for AstNode<'a, ExportSpecifier<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - let comments = f.context().comments().comments_before(self.exported.span().end); - let mut len = comments.len(); - while len != 0 && comments[len - 1].is_block() { - len -= 1; - } - if len != 0 { - write!(f, [FormatLeadingComments::Comments(&comments[..len])])?; - } + let comments = f.context().comments().line_comments_before(self.exported.span().end); + write!(f, [FormatLeadingComments::Comments(comments)])?; write!(f, [self.export_kind()]); if self.local.span() == self.exported.span() { diff --git a/crates/oxc_formatter/src/write/import_declaration.rs b/crates/oxc_formatter/src/write/import_declaration.rs index 81b72841005d9..5027c88551d8a 100644 --- a/crates/oxc_formatter/src/write/import_declaration.rs +++ b/crates/oxc_formatter/src/write/import_declaration.rs @@ -13,7 +13,6 @@ use crate::{ separated::FormatSeparatedIter, trivia::{FormatLeadingComments, FormatTrailingComments}, }, - utils::format_node_without_trailing_comments::FormatNodeWithoutTrailingComments, write, write::semicolon::OptionalSemicolon, }; @@ -31,7 +30,7 @@ pub fn format_import_and_export_source_with_clause<'a>( with_clause: Option<&AstNode<'a, WithClause>>, f: &mut Formatter<'_, 'a>, ) -> FormatResult<()> { - FormatNodeWithoutTrailingComments(source).fmt(f)?; + source.fmt(f)?; if let Some(with_clause) = with_clause { if f.comments().has_comment_before(with_clause.span.start) { @@ -145,15 +144,8 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, ImportDeclarationSpecifier<'a>>> { impl<'a> FormatWrite<'a> for AstNode<'a, ImportSpecifier<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - let comments = f.context().comments().comments_before(self.local.span.end); - let mut len = comments.len(); - while len != 0 && comments[len - 1].is_block() { - len -= 1; - } - if len != 0 { - write!(f, [FormatLeadingComments::Comments(&comments[..len])])?; - } - write!(f, [self.import_kind()])?; + let comments = f.context().comments().line_comments_before(self.local.span.end); + write!(f, [FormatLeadingComments::Comments(comments), self.import_kind()])?; if self.local.span == self.imported.span() { write!(f, [self.local()]) } else { diff --git a/crates/oxc_formatter/src/write/jsx/mod.rs b/crates/oxc_formatter/src/write/jsx/mod.rs index 431b75212ae28..4a1572fad316a 100644 --- a/crates/oxc_formatter/src/write/jsx/mod.rs +++ b/crates/oxc_formatter/src/write/jsx/mod.rs @@ -19,7 +19,6 @@ use crate::{ prelude::*, trivia::{DanglingIndentMode, FormatDanglingComments, FormatTrailingComments}, }, - utils::format_node_without_trailing_comments::FormatNodeWithoutTrailingComments, write, }; @@ -206,20 +205,14 @@ impl<'a> FormatWrite<'a> for AstNode<'a, JSXExpressionContainer<'a>> { // JSXAttributeValue let should_inline = !has_comment(f) && should_inline_jsx_expression(self, f.comments()); - let format_expression = format_once(|f| { - write!(f, FormatNodeWithoutTrailingComments(&self.expression())); - let comments = f.context().comments().comments_before(self.span.end); - write!(f, FormatTrailingComments::Comments(comments)) - }); - if should_inline { - write!(f, ["{", format_expression, line_suffix_boundary(), "}"]) + write!(f, ["{", self.expression(), line_suffix_boundary(), "}"]) } else { write!( f, [group(&format_args!( "{", - soft_block_indent(&format_expression), + soft_block_indent(&self.expression()), line_suffix_boundary(), "}" ))] diff --git a/crates/oxc_formatter/src/write/mod.rs b/crates/oxc_formatter/src/write/mod.rs index 7983e3cef7294..3bd23890a99d5 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -252,13 +252,7 @@ impl<'a> FormatWrite<'a> for AstNode<'a, CallExpression<'a>> { impl<'a> FormatWrite<'a> for AstNode<'a, NewExpression<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - write!(f, ["new", space()]); - if self.type_arguments.is_some() { - write!(f, [self.callee()]); - } else { - write!(f, [FormatNodeWithoutTrailingComments(self.callee())]); - } - write!(f, [self.type_arguments(), self.arguments()]) + write!(f, ["new", space(), self.callee(), self.type_arguments(), self.arguments()]) } } @@ -962,12 +956,7 @@ impl<'a> FormatWrite<'a> for AstNode<'a, AssignmentPattern<'a>> { impl<'a> FormatWrite<'a> for AstNode<'a, ObjectPattern<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if matches!(self.parent, AstNodes::FormalParameter(param) if param.pattern.type_annotation.is_some()) - { - FormatNodeWithoutTrailingComments(&ObjectPatternLike::ObjectPattern(self)).fmt(f) - } else { - ObjectPatternLike::ObjectPattern(self).fmt(f) - } + ObjectPatternLike::ObjectPattern(self).fmt(f) } } @@ -1210,16 +1199,7 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSTypeOperator<'a>> { impl<'a> FormatWrite<'a> for AstNode<'a, TSArrayType<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if let AstNodes::TSUnionType(union) = self.element_type().as_ast_nodes() { - // `TSUnionType` has special logic for comments, so we need to delegate to it. - union.fmt(f)?; - } else { - FormatNodeWithoutTrailingComments(self.element_type()).fmt(f)?; - } - let comments = - f.context().comments().comments_before_character(self.element_type.span().end, b'['); - FormatTrailingComments::Comments(comments).fmt(f)?; - write!(f, ["[]"]) + write!(f, [self.element_type(), "[]"]) } } diff --git a/crates/oxc_formatter/src/write/object_pattern_like.rs b/crates/oxc_formatter/src/write/object_pattern_like.rs index f5e838039e2e7..1c3b26f3f2834 100644 --- a/crates/oxc_formatter/src/write/object_pattern_like.rs +++ b/crates/oxc_formatter/src/write/object_pattern_like.rs @@ -24,20 +24,7 @@ pub enum ObjectPatternLike<'a, 'b> { impl GetSpan for ObjectPatternLike<'_, '_> { fn span(&self) -> Span { match self { - Self::ObjectPattern(node) => { - // `{a, b}: {a: number, b: string}` - // ^^^^^^^^^^^^^^ ObjectPattern's span covers the type annotation if exists, - // ^^^^^^ but we want the span to cover only the pattern itself, otherwise, - // the comments of type annotation will be treated as dangling comments - // of ObjectPattern. - if let AstNodes::FormalParameter(param) = node.parent - && let Some(ty) = ¶m.pattern.type_annotation - { - Span::new(node.span.start, ty.span.start) - } else { - node.span - } - } + Self::ObjectPattern(node) => node.span, Self::ObjectAssignmentTarget(node) => node.span, } } diff --git a/crates/oxc_formatter/src/write/program.rs b/crates/oxc_formatter/src/write/program.rs index 7afb01b72d348..d5d7cf874762c 100644 --- a/crates/oxc_formatter/src/write/program.rs +++ b/crates/oxc_formatter/src/write/program.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use std::{ops::Deref, ptr::dangling}; use oxc_allocator::{Address, Vec}; use oxc_ast::{ast::*, match_expression}; @@ -25,8 +25,7 @@ use super::FormatWrite; impl<'a> FormatWrite<'a> for AstNode<'a, Program<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let format_trailing_comments = format_once(|f| { - let comments = f.context().comments().comments_before(self.span.end); - write!(f, FormatTrailingComments::Comments(comments)) + write!(f, FormatTrailingComments::Comments(f.context().comments().unprinted_comments())) }); write!( diff --git a/tasks/ast_tools/src/generators/formatter/ast_nodes.rs b/tasks/ast_tools/src/generators/formatter/ast_nodes.rs index 7336f7cad0f94..bef2f1ab1deb7 100644 --- a/tasks/ast_tools/src/generators/formatter/ast_nodes.rs +++ b/tasks/ast_tools/src/generators/formatter/ast_nodes.rs @@ -23,13 +23,7 @@ pub fn get_node_type(ty: &TokenStream) -> TokenStream { /// 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] = &[ - "AssignmentExpression", - "FormalParameters", - "StaticMemberExpression", - "ComputedMemberExpression", - "ObjectProperty", -]; +const AST_NODE_WITHOUT_FOLLOWING_NODE_LIST: &[&str] = &[]; const AST_NODE_WITH_FOLLOWING_NODE_LIST: &[&str] = &["Function", "Class"];