diff --git a/crates/oxc_formatter/src/ast_nodes/generated/format.rs b/crates/oxc_formatter/src/ast_nodes/generated/format.rs index e83a5e9861f94..65a91d048e2d6 100644 --- a/crates/oxc_formatter/src/ast_nodes/generated/format.rs +++ b/crates/oxc_formatter/src/ast_nodes/generated/format.rs @@ -4202,13 +4202,11 @@ impl<'a> Format<'a> for AstNode<'a, TSTypeAliasDeclaration<'a>> { impl<'a> Format<'a> for AstNode<'a, TSClassImplements<'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 6cbdfe24bc97c..98b679558bbfe 100644 --- a/crates/oxc_formatter/src/formatter/comments.rs +++ b/crates/oxc_formatter/src/formatter/comments.rs @@ -312,7 +312,6 @@ impl<'a> Comments<'a> { let Some(following_span) = following_span else { // Find dangling comments at the end of the enclosing node let comments = self.comments_before(enclosing_span.end); - let mut start = preceding_span.end; for (idx, comment) in comments.iter().enumerate() { // Comments inside the preceding node, which should be printed without checking @@ -333,22 +332,32 @@ impl<'a> Comments<'a> { }; let mut comment_index = 0; - while let Some(comment) = comments.get(comment_index) { - // Check if the comment is before the following node's span - if comment.span.end > following_span.start { - break; - } + let mut type_cast_comment = None; - if matches!(comment.content, CommentContent::Jsdoc) - && self.is_type_cast_comment(comment) + while let Some(comment) = comments.get(comment_index) { + // Stop if the comment: + // 1. is over the following node + // 2. is after the enclosing node, which means the comment should be printed in the parent node. + if comment.span.end > following_span.start + || (comment.span.end > enclosing_span.end && enclosing_span != preceding_span) { break; } - if self.is_own_line_comment(comment) { - // Own line comments are typically leading comments for the next node + if following_span.start > enclosing_span.end && comment.span.end <= enclosing_span.end { + // Do nothing; this comment is inside the enclosing node, and the following node is outside the enclosing node. + // So it must be a trailing comment, continue checking the next comment. + } else if self.is_type_cast_comment(comment) { + // `A || /* @type {Number} */ (B)`: + // ^^^^^^^^^^^^^^^^^^^^^^^^ + // Type cast comments should always be treated as leading comment to the following node + type_cast_comment = Some(comment); + break; + } else if self.is_own_line_comment(comment) { + // Own-line comments should be treated as leading comments to the following node break; } else if self.is_end_of_line_comment(comment) { + //End-of-line comments are always trailing comments to the preceding node. return &comments[..=comment_index]; } @@ -356,7 +365,8 @@ impl<'a> Comments<'a> { } // Find the first comment (from the end) that has non-whitespace/non-paren content after it - let mut gap_end = following_span.start; + let mut gap_end = type_cast_comment.map_or(following_span.start, |c| c.span.start); + for (idx, comment) in comments[..comment_index].iter().enumerate().rev() { if source_text.all_bytes_match(comment.span.end, gap_end, |b| { b.is_ascii_whitespace() || b == b'(' diff --git a/crates/oxc_formatter/src/write/binary_like_expression.rs b/crates/oxc_formatter/src/write/binary_like_expression.rs index d58ea0097cb59..6b4311cf91b2f 100644 --- a/crates/oxc_formatter/src/write/binary_like_expression.rs +++ b/crates/oxc_formatter/src/write/binary_like_expression.rs @@ -13,7 +13,6 @@ use crate::{ ast_nodes::{AstNode, AstNodes}, formatter::{FormatResult, Formatter, trivia::FormatTrailingComments}, parentheses::NeedsParentheses, - utils::format_node_without_trailing_comments::FormatNodeWithoutTrailingComments, }; use crate::{format_args, formatter::prelude::*, write}; @@ -294,8 +293,6 @@ enum BinaryLeftOrRightSide<'a, 'b> { parent: BinaryLikeExpression<'a, 'b>, /// Is the parent the condition of a `if` / `while` / `do-while` / `for` statement? inside_condition: bool, - /// It is the root of the expression. - root: bool, }, } @@ -306,7 +303,6 @@ impl<'a> Format<'a> for BinaryLeftOrRightSide<'a, '_> { Self::Right { parent: binary_like_expression, inside_condition: inside_parenthesis, - root, } => { let mut binary_like_expression = *binary_like_expression; // // It's only possible to suppress the formatting of the whole binary expression formatting OR @@ -401,16 +397,7 @@ impl<'a> Format<'a> for BinaryLeftOrRightSide<'a, '_> { write!(f, [soft_line_break_or_space()])?; } - if *root { - write!(f, FormatNodeWithoutTrailingComments(right))?; - let comments = f - .context() - .comments() - .comments_before(binary_like_expression.span().end); - write!(f, FormatTrailingComments::Comments(comments)) - } else { - write!(f, right) - } + write!(f, right) }); // Doesn't match prettier that only distinguishes between logical and binary @@ -425,6 +412,16 @@ impl<'a> Format<'a> for BinaryLeftOrRightSide<'a, '_> { right.as_ast_nodes(), ) || (*inside_parenthesis && logical_operator.is_some())); + match binary_like_expression.left().as_ast_nodes() { + AstNodes::LogicalExpression(logical) => { + logical.format_trailing_comments(f)?; + } + AstNodes::BinaryExpression(binary) => { + binary.format_trailing_comments(f)?; + } + _ => {} + } + if should_group { // `left` side has printed before `right` side, so that trailing comments of `left` side has been printed, // so we need to find if there are any printed comments that are after the `left` side and it is line comment. @@ -489,7 +486,6 @@ fn split_into_left_and_right_sides<'a, 'b>( inside_condition: bool, ) -> Vec> { fn split_into_left_and_right_sides_inner<'a, 'b>( - is_root: bool, binary: BinaryLikeExpression<'a, 'b>, inside_condition: bool, items: &mut Vec>, @@ -500,7 +496,6 @@ fn split_into_left_and_right_sides<'a, 'b>( // We can flatten the left hand side, so we need to check if we have a nested binary expression // that we can flatten. split_into_left_and_right_sides_inner( - false, // SAFETY: `left` is guaranteed to be a valid binary like expression in `can_flatten()`. BinaryLikeExpression::try_from(left).unwrap(), inside_condition, @@ -510,11 +505,7 @@ fn split_into_left_and_right_sides<'a, 'b>( items.push(BinaryLeftOrRightSide::Left { parent: binary }); } - items.push(BinaryLeftOrRightSide::Right { - parent: binary, - inside_condition, - root: is_root, - }); + items.push(BinaryLeftOrRightSide::Right { parent: binary, inside_condition }); } // Stores the left and right parts of the binary expression in sequence (rather than nested as they @@ -522,7 +513,7 @@ fn split_into_left_and_right_sides<'a, 'b>( // `with_capacity(2)` because we expect at least 2 items (left and right). let mut items = Vec::with_capacity(2); - split_into_left_and_right_sides_inner(true, binary, inside_condition, &mut items); + split_into_left_and_right_sides_inner(binary, inside_condition, &mut items); items } diff --git a/crates/oxc_formatter/src/write/class.rs b/crates/oxc_formatter/src/write/class.rs index 634268ff05671..61ee56d888868 100644 --- a/crates/oxc_formatter/src/write/class.rs +++ b/crates/oxc_formatter/src/write/class.rs @@ -235,14 +235,22 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, TSClassImplements<'a>>> { group(&indent(&format_args!( soft_line_break_or_space(), format_once(|f| { - // the grouping will be applied by the parent - f.join_with(&soft_line_break_or_space()) - .entries_with_trailing_separator( - self.iter(), - ",", - TrailingSeparator::Disallowed, - ) - .finish() + let last_index = self.len().saturating_sub(1); + let mut joiner = f.join_with(soft_line_break_or_space()); + + for (i, heritage) in FormatSeparatedIter::new(self.into_iter(), ",") + .with_trailing_separator(TrailingSeparator::Disallowed) + .enumerate() + { + if i == last_index { + // The trailing comments of the last heritage should be printed inside the class declaration + joiner.entry(&FormatNodeWithoutTrailingComments(&heritage)); + } else { + joiner.entry(&heritage); + } + } + + joiner.finish() }) ))) ] diff --git a/crates/oxc_formatter/src/write/mod.rs b/crates/oxc_formatter/src/write/mod.rs index 9b970a086a391..7983e3cef7294 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -1628,6 +1628,7 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, TSInterfaceHeritage<'a>>> { .enumerate() { if i == last_index { + // The trailing comments of the last heritage should be printed inside the interface declaration joiner.entry(&FormatNodeWithoutTrailingComments(&heritage)); } else { joiner.entry(&heritage); diff --git a/crates/oxc_formatter/tests/fixtures/js/comments/type-cast-node.js.snap b/crates/oxc_formatter/tests/fixtures/js/comments/type-cast-node.js.snap index d78abf270cb1b..7fd2efad58bda 100644 --- a/crates/oxc_formatter/tests/fixtures/js/comments/type-cast-node.js.snap +++ b/crates/oxc_formatter/tests/fixtures/js/comments/type-cast-node.js.snap @@ -23,8 +23,8 @@ source: crates/oxc_formatter/tests/fixtures/mod.rs (a) === "call" || /** @type {Identifier} */ (b) === "bind" + // ^^^^^^^^^^^^^^ No need to wrap with parentheses here because the type cast node is already wrapped with parentheses. ) && - // ^^^^^^^^^^^^^^ No need to wrap with parentheses here because the type cast node is already wrapped with parentheses. right; /** @type {Number} */ (a + b)(); diff --git a/tasks/ast_tools/src/generators/formatter/format.rs b/tasks/ast_tools/src/generators/formatter/format.rs index f1ea05b4b13f1..7cfe6c1d5653e 100644 --- a/tasks/ast_tools/src/generators/formatter/format.rs +++ b/tasks/ast_tools/src/generators/formatter/format.rs @@ -24,7 +24,6 @@ const AST_NODE_WITHOUT_PRINTING_COMMENTS_LIST: &[&str] = &[ // Manually prints it because class's decorators can be appears before `export class Cls {}`. "ExportNamedDeclaration", "ExportDefaultDeclaration", - "TSClassImplements", // "JSXElement", "JSXFragment",