diff --git a/crates/oxc_formatter/src/ast_nodes/generated/format.rs b/crates/oxc_formatter/src/ast_nodes/generated/format.rs index d649f3b5744ad..a21aecdf14cf8 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/format.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/format.rs @@ -4802,13 +4802,11 @@ impl<'a> Format<'a> for AstNode<'a, TSNonNullExpression<'a>> { impl<'a> Format<'a> for AstNode<'a, Decorator<'a>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let is_suppressed = f.comments().is_suppressed(self.span().start); - if is_suppressed { - self.format_leading_comments(f)?; - FormatSuppressedNode(self.span()).fmt(f)?; - self.format_trailing_comments(f) - } else { - self.write(f) - } + self.format_leading_comments(f)?; + let result = + if is_suppressed { FormatSuppressedNode(self.span()).fmt(f) } else { self.write(f) }; + self.format_trailing_comments(f)?; + result } } diff --git a/crates/oxc_formatter/src/formatter/comments.rs b/crates/oxc_formatter/src/formatter/comments.rs index d3933a40380aa..0741fee04c7ac 100644 --- a/crates/oxc_formatter/src/formatter/comments.rs +++ b/crates/oxc_formatter/src/formatter/comments.rs @@ -179,10 +179,8 @@ impl<'a> Comments<'a> { /// 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 = self - .comments_before_iter(pos) - .take_while(|c| self.source_text.is_own_line_comment(c)) - .count(); + let index = + self.comments_before_iter(pos).take_while(|c| self.is_own_line_comment(c)).count(); &self.unprinted_comments()[..index] } @@ -191,9 +189,9 @@ impl<'a> Comments<'a> { let comments = self.unprinted_comments(); for (index, comment) in comments.iter().enumerate() { if self.source_text.all_bytes_match(pos, comment.span.start, |b| { - matches!(b, b'\t' | b' ' | b')' | b'=') + matches!(b, b'\t' | b' ') || b.is_ascii_punctuation() }) { - if comment.is_line() || self.source_text.is_end_of_line_comment(comment) { + if comment.is_line() || self.is_end_of_line_comment(comment) { return &comments[..=index]; } pos = comment.span.end; @@ -347,10 +345,10 @@ impl<'a> Comments<'a> { break; } - if source_text.is_own_line_comment(comment) { + if self.is_own_line_comment(comment) { // Own line comments are typically leading comments for the next node break; - } else if self.source_text.is_end_of_line_comment(comment) { + } else if self.is_end_of_line_comment(comment) { return &comments[..=comment_index]; } @@ -410,6 +408,14 @@ impl<'a> Comments<'a> { false } + pub fn is_own_line_comment(&self, comment: &Comment) -> bool { + self.source_text.has_newline_before(comment.span.start) + } + + pub fn is_end_of_line_comment(&self, comment: &Comment) -> bool { + self.source_text.has_newline_after(comment.span.end) + } + /// Finds the index of a type cast comment before the given span. /// /// Searches for a JSDoc comment containing @type or @satisfies that is followed diff --git a/crates/oxc_formatter/src/formatter/source_text.rs b/crates/oxc_formatter/src/formatter/source_text.rs index 06eb3d42f0b2c..0a891d81b2e43 100644 --- a/crates/oxc_formatter/src/formatter/source_text.rs +++ b/crates/oxc_formatter/src/formatter/source_text.rs @@ -205,12 +205,4 @@ impl<'a> SourceText<'a> { 0 } - - pub fn is_own_line_comment(&self, comment: &Comment) -> bool { - self.has_newline_before(comment.span.start) - } - - pub fn is_end_of_line_comment(&self, comment: &Comment) -> bool { - self.has_newline_after(comment.span.end) - } } diff --git a/crates/oxc_formatter/src/utils/assignment_like.rs b/crates/oxc_formatter/src/utils/assignment_like.rs index 8046fde1a27af..c4067105cb98d 100644 --- a/crates/oxc_formatter/src/utils/assignment_like.rs +++ b/crates/oxc_formatter/src/utils/assignment_like.rs @@ -162,7 +162,7 @@ fn format_left_trailing_comments( let comments = if end_of_line_comments.is_empty() { let comments = f.context().comments().comments_before_character(start, b'='); - if comments.iter().any(|c| f.source_text().is_own_line_comment(c)) { &[] } else { comments } + if comments.iter().any(|c| f.comments().is_own_line_comment(c)) { &[] } else { comments } } else if should_print_as_leading || end_of_line_comments.last().is_some_and(|c| c.is_block()) { // No trailing comments for these expressions or if the trailing comment is a block comment &[] @@ -233,7 +233,6 @@ impl<'a> AssignmentLike<'a, '_> { } } AssignmentLike::PropertyDefinition(property) => { - write!(f, property.decorators())?; if property.declare { write!(f, ["declare", space()])?; } diff --git a/crates/oxc_formatter/src/utils/conditional.rs b/crates/oxc_formatter/src/utils/conditional.rs index 4224edede7adc..b53d1fd55dec4 100644 --- a/crates/oxc_formatter/src/utils/conditional.rs +++ b/crates/oxc_formatter/src/utils/conditional.rs @@ -146,7 +146,7 @@ fn format_trailing_comments<'a>( return &comments[..index]; } // If this comment is a line comment or an end of line comment, so we stop here and return the comments with this comment - else if comment.is_line() || source_text.is_end_of_line_comment(comment) { + else if comment.is_line() || f.comments().is_end_of_line_comment(comment) { return &comments[..=index]; } // Store the index of the comment before the operator, if no line comment or no new line is found, then return all comments before operator diff --git a/crates/oxc_formatter/src/write/array_element_list.rs b/crates/oxc_formatter/src/write/array_element_list.rs index 6f9bea8fa18ba..fee83d21fc717 100644 --- a/crates/oxc_formatter/src/write/array_element_list.rs +++ b/crates/oxc_formatter/src/write/array_element_list.rs @@ -117,7 +117,6 @@ pub fn can_concisely_print_array_list( return false; } - let source_text = f.source_text(); let comments = f.comments(); let mut comments_iter = comments.comments_before_iter(array_expression_span.end); @@ -151,11 +150,10 @@ pub fn can_concisely_print_array_list( // ] // ``` - let source_text = f.source_text(); !comments.comments_before_iter(array_expression_span.end).any(|comment| { comment.is_line() - && !source_text.is_own_line_comment(comment) - && source_text.is_end_of_line_comment(comment) + && !comments.is_own_line_comment(comment) + && comments.is_end_of_line_comment(comment) }) } diff --git a/crates/oxc_formatter/src/write/class.rs b/crates/oxc_formatter/src/write/class.rs index 76d331da0a965..73620f39524c1 100644 --- a/crates/oxc_formatter/src/write/class.rs +++ b/crates/oxc_formatter/src/write/class.rs @@ -54,16 +54,25 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, ClassElement<'a>>> { impl<'a> Format<'a> for (&AstNode<'a, ClassElement<'a>>, Option<&AstNode<'a, ClassElement<'a>>>) { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - write!(f, [self.0, ClassPropertySemicolon::new(self.0, self.1)]) + let decorators = match self.0.as_ast_nodes() { + AstNodes::MethodDefinition(method) => { + write!(f, [method.decorators(), method]) + } + AstNodes::PropertyDefinition(property) => { + write!(f, [property.decorators(), property]) + } + AstNodes::AccessorProperty(accessor) => { + write!(f, [accessor.decorators(), accessor]) + } + _ => write!(f, self.0), + }; + + write!(f, [ClassPropertySemicolon::new(self.0, self.1)]) } } impl<'a> FormatWrite<'a> for AstNode<'a, MethodDefinition<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - // Write modifiers in the correct order: - // decorators -> accessibility -> static -> abstract -> override -> async -> generator - write!(f, [self.decorators()])?; - if let Some(accessibility) = &self.accessibility { write!(f, [accessibility.as_str(), space()])?; } @@ -157,8 +166,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, StaticBlock<'a>> { impl<'a> FormatWrite<'a> for AstNode<'a, AccessorProperty<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - write!(f, self.decorators())?; - if let Some(accessibility) = self.accessibility() { write!(f, [accessibility.as_str(), space()])?; } diff --git a/crates/oxc_formatter/src/write/decorators.rs b/crates/oxc_formatter/src/write/decorators.rs index f33998443fe7d..05e9b7f776df0 100644 --- a/crates/oxc_formatter/src/write/decorators.rs +++ b/crates/oxc_formatter/src/write/decorators.rs @@ -22,42 +22,37 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, Decorator<'a>>> { return Ok(()); }; - let format_decorators = format_once(|f| { - // Check parent to determine formatting context - match self.parent { - AstNodes::PropertyDefinition(_) - | AstNodes::MethodDefinition(_) - | AstNodes::AccessorProperty(_) => { - return write!( - f, - [group(&format_args!( - format_once(|f| { - f.join_nodes_with_soft_line().entries(self.iter()).finish() - }), - soft_line_break_or_space() - )) - .should_expand(should_expand_decorators(self, f))] - ); - } - // Parameter decorators - AstNodes::FormalParameter(_) => { - write!(f, should_expand_decorators(self, f).then_some(expand_parent()))?; - } - AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => { - write!(f, [hard_line_break()])?; - } - _ => { - write!(f, [expand_parent()])?; - } + // Check parent to determine formatting context + match self.parent { + AstNodes::PropertyDefinition(_) + | AstNodes::MethodDefinition(_) + | AstNodes::AccessorProperty(_) => { + return write!( + f, + [group(&format_args!( + format_once(|f| { + f.join_nodes_with_soft_line().entries(self.iter()).finish() + }), + soft_line_break_or_space() + )) + .should_expand(should_expand_decorators(self, f))] + ); } + // Parameter decorators + AstNodes::FormalParameter(_) => { + write!(f, should_expand_decorators(self, f).then_some(expand_parent()))?; + } + AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => { + write!(f, [hard_line_break()])?; + } + _ => { + write!(f, [expand_parent()])?; + } + } - f.join_with(&soft_line_break_or_space()).entries(self.iter()).finish()?; - - write!(f, [soft_line_break_or_space()]) - }); + f.join_with(&soft_line_break_or_space()).entries(self.iter()).finish()?; - format_decorators.fmt(f)?; - format_trailing_comments_for_last_decorator(last.span.end, f) + write!(f, [soft_line_break_or_space()]) } } @@ -114,32 +109,3 @@ fn should_expand_decorators<'a>( ) -> bool { decorators.iter().any(|decorator| f.source_text().lines_after(decorator.span().end) > 0) } - -pub fn format_trailing_comments_for_last_decorator( - mut start: u32, - f: &mut Formatter<'_, '_>, -) -> FormatResult<()> { - let mut comments = f.context().comments().unprinted_comments(); - - for (i, comment) in comments.iter().enumerate() { - if !f.source_text().all_bytes_match(start, comment.span.start, |b| b.is_ascii_whitespace()) - { - comments = &comments[..i]; - break; - } - - start = comment.span.end; - } - - if !comments.is_empty() { - write!( - f, - [group(&format_args!( - FormatTrailingComments::Comments(comments), - soft_line_break_or_space() - ))] - )?; - } - - Ok(()) -} diff --git a/crates/oxc_formatter/src/write/function.rs b/crates/oxc_formatter/src/write/function.rs index dbe5af0ae012d..05f8086cf70d8 100644 --- a/crates/oxc_formatter/src/write/function.rs +++ b/crates/oxc_formatter/src/write/function.rs @@ -42,8 +42,8 @@ impl<'a> Deref for FormatFunction<'a, '_> { } } -impl<'a> FormatWrite<'a> for FormatFunction<'a, '_> { - fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { +impl<'a> Format<'a> for FormatFunction<'a, '_> { + fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { let head = format_once(|f| { write!( f, @@ -148,7 +148,7 @@ impl<'a> FormatWrite<'a> for FormatFunction<'a, '_> { impl<'a> FormatWrite<'a, FormatFunctionOptions> for AstNode<'a, Function<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - FormatFunction { function: self, options: FormatFunctionOptions::default() }.write(f) + FormatFunction { function: self, options: FormatFunctionOptions::default() }.fmt(f) } fn write_with_options( @@ -156,7 +156,7 @@ impl<'a> FormatWrite<'a, FormatFunctionOptions> for AstNode<'a, Function<'a>> { options: FormatFunctionOptions, f: &mut Formatter<'_, 'a>, ) -> FormatResult<()> { - FormatFunction { function: self, options }.write(f) + FormatFunction { function: self, options }.fmt(f) } } diff --git a/crates/oxc_formatter/src/write/import_declaration.rs b/crates/oxc_formatter/src/write/import_declaration.rs index 74f2b27a8a467..81b72841005d9 100644 --- a/crates/oxc_formatter/src/write/import_declaration.rs +++ b/crates/oxc_formatter/src/write/import_declaration.rs @@ -13,6 +13,7 @@ use crate::{ separated::FormatSeparatedIter, trivia::{FormatLeadingComments, FormatTrailingComments}, }, + utils::format_node_without_trailing_comments::FormatNodeWithoutTrailingComments, write, write::semicolon::OptionalSemicolon, }; @@ -30,8 +31,7 @@ pub fn format_import_and_export_source_with_clause<'a>( with_clause: Option<&AstNode<'a, WithClause>>, f: &mut Formatter<'_, 'a>, ) -> FormatResult<()> { - source.format_leading_comments(f)?; - source.write(f)?; + FormatNodeWithoutTrailingComments(source).fmt(f)?; if let Some(with_clause) = with_clause { if f.comments().has_comment_before(with_clause.span.start) { diff --git a/crates/oxc_formatter/src/write/parameters.rs b/crates/oxc_formatter/src/write/parameters.rs index 4c9ae8212a8a2..ae507ba23d318 100644 --- a/crates/oxc_formatter/src/write/parameters.rs +++ b/crates/oxc_formatter/src/write/parameters.rs @@ -27,6 +27,8 @@ pub fn get_this_param<'a>(parent: &AstNodes<'a>) -> Option<&'a AstNode<'a, TSThi impl<'a> FormatWrite<'a> for AstNode<'a, FormalParameters<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { + // `function foo /**/ () {}` + // ^^^ keep comments printed before parameters let comments = f.context().comments().comments_before(self.span.start); if !comments.is_empty() { write!(f, [space(), FormatTrailingComments::Comments(comments)])?; @@ -77,13 +79,9 @@ impl<'a> FormatWrite<'a> for AstNode<'a, FormalParameters<'a>> { ParameterLayout::Default => { write!( f, - soft_block_indent(&format_args!( - &ParameterList::with_layout(self, this_param, layout), - format_once(|f| { - let comments = f.context().comments().comments_before(self.span.end); - write!(f, [FormatTrailingComments::Comments(comments)]) - }) - )) + soft_block_indent(&format_args!(&ParameterList::with_layout( + self, this_param, layout + ))) ); } } diff --git a/crates/oxc_formatter/src/write/return_or_throw_statement.rs b/crates/oxc_formatter/src/write/return_or_throw_statement.rs index 2ed4f4ee870d9..ae7fbda6b89ff 100644 --- a/crates/oxc_formatter/src/write/return_or_throw_statement.rs +++ b/crates/oxc_formatter/src/write/return_or_throw_statement.rs @@ -121,19 +121,18 @@ fn has_argument_leading_comments(argument: &AstNode, f: &Formatter<' for left_side in ExpressionLeftSide::from(argument).iter() { let start = left_side.span().start; - let comments = f.comments().comments_before(start); + let comments = f.context().comments(); + let leading_comments = comments.comments_before(start); - if f.comments().comments_before_iter(start).any(|comment| { - source_text.contains_newline(comment.span) - || source_text.is_end_of_line_comment(comment) + if leading_comments.iter().any(|comment| { + source_text.contains_newline(comment.span) || comments.is_end_of_line_comment(comment) }) { return true; } - let is_own_line_comment_or_multi_line_comment = |comments: &[Comment]| { - comments.iter().any(|comment| { - source_text.is_own_line_comment(comment) - || source_text.contains_newline(comment.span) + let is_own_line_comment_or_multi_line_comment = |leading_comments: &[Comment]| { + leading_comments.iter().any(|comment| { + comments.is_own_line_comment(comment) || source_text.contains_newline(comment.span) }) }; diff --git a/crates/oxc_formatter/src/write/switch_statement.rs b/crates/oxc_formatter/src/write/switch_statement.rs index fc1b88d5c906a..3a33749368eca 100644 --- a/crates/oxc_formatter/src/write/switch_statement.rs +++ b/crates/oxc_formatter/src/write/switch_statement.rs @@ -10,7 +10,7 @@ use crate::{ formatter::{ Formatter, prelude::*, - trivia::{DanglingIndentMode, FormatDanglingComments, FormatTrailingComments}, + trivia::{DanglingIndentMode, FormatDanglingComments}, }, utils::statement_body::FormatStatementBody, write, @@ -114,7 +114,9 @@ impl<'a> FormatWrite<'a> for AstNode<'a, SwitchCase<'a>> { let comments = if is_single_block_statement { comments.block_comments_before(first_statement.span().start) } else { - comments.comments_before_character(self.span.start, b'\n') + #[expect(clippy::cast_possible_truncation)] + const DEFAULT_LEN: u32 = "default".len() as u32; + comments.end_of_line_comments_after(self.span.start + DEFAULT_LEN) }; if !comments.is_empty() { diff --git a/crates/oxc_formatter/src/write/try_statement.rs b/crates/oxc_formatter/src/write/try_statement.rs index 308b406551792..63e5571b9ed3f 100644 --- a/crates/oxc_formatter/src/write/try_statement.rs +++ b/crates/oxc_formatter/src/write/try_statement.rs @@ -43,11 +43,12 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TryStatement<'a>> { impl<'a> FormatWrite<'a> for AstNode<'a, CatchClause<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - let comments = f.context().comments().comments_before(self.span.start); - let has_line_comment = comments.iter().any(|comment| { + let comments = f.context().comments(); + let leading_comments = comments.comments_before(self.span.start); + let has_line_comment = leading_comments.iter().any(|comment| { comment.is_line() - || f.source_text().is_own_line_comment(comment) - || f.source_text().is_end_of_line_comment(comment) + || comments.is_own_line_comment(comment) + || comments.is_end_of_line_comment(comment) }); if has_line_comment { @@ -57,13 +58,13 @@ impl<'a> FormatWrite<'a> for AstNode<'a, CatchClause<'a>> { // // Comments before the catch clause should be printed in the block statement. // We cache them here to avoid the `params` printing them accidentally. - let printed_comments = f.intern(&FormatLeadingComments::Comments(comments)); + let printed_comments = f.intern(&FormatLeadingComments::Comments(leading_comments)); if let Ok(Some(comments)) = printed_comments { f.context_mut().cache_element(&self.span, comments); } - } else if !comments.is_empty() { + } else if !leading_comments.is_empty() { // otherwise, print them before `catch` - write!(f, [FormatTrailingComments::Comments(comments), space()]); + write!(f, [FormatTrailingComments::Comments(leading_comments), space()]); } write!(f, ["catch", space(), self.param(), space()])?; diff --git a/tasks/ast_tools/src/generators/formatter/format.rs b/tasks/ast_tools/src/generators/formatter/format.rs index 8dacf7fccbf18..dc7b4f42f316d 100644 --- a/tasks/ast_tools/src/generators/formatter/format.rs +++ b/tasks/ast_tools/src/generators/formatter/format.rs @@ -21,7 +21,6 @@ const AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST: &[&str] = &[ "ClassBody", "CatchParameter", "CatchClause", - "Decorator", // Manually prints it because class's decorators can be appears before `export class Cls {}`. "ExportNamedDeclaration", "ExportDefaultDeclaration",