diff --git a/crates/oxc_formatter/src/write/call_arguments.rs b/crates/oxc_formatter/src/write/call_arguments.rs index 563bc8b76bb1b..a7ff6a03a7a98 100644 --- a/crates/oxc_formatter/src/write/call_arguments.rs +++ b/crates/oxc_formatter/src/write/call_arguments.rs @@ -98,7 +98,10 @@ impl<'a> Format<'a> for AstNode<'a, ArenaVec<'a, Argument<'a>>> { let has_empty_line = self.iter().any(|arg| f.source_text().get_lines_before(arg.span(), f.comments()) > 1); - if has_empty_line || is_function_composition_args(self) { + if has_empty_line + || (!matches!(self.parent.parent(), AstNodes::Decorator(_)) + && is_function_composition_args(self)) + { return format_all_args_broken_out(self, true, f); } diff --git a/crates/oxc_formatter/src/write/class.rs b/crates/oxc_formatter/src/write/class.rs index 398e6a49e909d..f997ec19d5516 100644 --- a/crates/oxc_formatter/src/write/class.rs +++ b/crates/oxc_formatter/src/write/class.rs @@ -60,7 +60,8 @@ impl<'a> FormatWrite<'a> for AstNode<'a, MethodDefinition<'a>> { // Write modifiers in the correct order: // decorators -> accessibility -> static -> abstract -> override -> async -> generator write!(f, [self.decorators()])?; - if let Some(accessibility) = &self.accessibility() { + + if let Some(accessibility) = &self.accessibility { write!(f, [accessibility.as_str(), space()])?; } if self.r#static { @@ -157,11 +158,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())?; - // Handle comments between decorators and the 'accessor' keyword - // We look for comments before the first character 'a' of 'accessor' - // This ensures proper placement of comments like: @decorator /* comment */ accessor x - let comments = f.context().comments().comments_before_character(self.span.start, b'a'); - FormatLeadingComments::Comments(comments).fmt(f)?; 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 2f8d6f60788eb..031536346e9ed 100644 --- a/crates/oxc_formatter/src/write/decorators.rs +++ b/crates/oxc_formatter/src/write/decorators.rs @@ -4,7 +4,11 @@ use oxc_span::GetSpan; use crate::{ Format, FormatResult, format_args, - formatter::{Formatter, prelude::*}, + formatter::{ + Formatter, + prelude::*, + trivia::{FormatLeadingComments, FormatTrailingComments}, + }, generated::ast_nodes::{AstNode, AstNodes}, write, }; @@ -13,40 +17,46 @@ use super::FormatWrite; impl<'a> Format<'a> for AstNode<'a, Vec<'a, Decorator<'a>>> { fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { - if self.is_empty() { + let Some(last) = self.last() else { return Ok(()); - } + }; - // 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()])?; + 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()])?; + } } - } - 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()?; + + write!(f, [soft_line_break_or_space()]) + }); + + format_decorators.fmt(f)?; + format_trailing_comments_for_last_decorator(last.span.end, f) } } @@ -103,3 +113,32 @@ 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/mod.rs b/crates/oxc_formatter/src/write/mod.rs index 407f3f9ed5ae7..5cbbb75f6cd46 100644 --- a/crates/oxc_formatter/src/write/mod.rs +++ b/crates/oxc_formatter/src/write/mod.rs @@ -1725,6 +1725,9 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSFunctionType<'a>> { write!(f, params)?; } + let comments = f.context().comments().comments_before_character(params.span.end, b'='); + FormatTrailingComments::Comments(comments).fmt(f)?; + write!(f, [space(), "=>", space(), return_type.type_annotation()]) }); @@ -1756,6 +1759,11 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> { space(), type_parameters, params, + format_once(|f| { + let comments = + f.context().comments().comments_before_character(params.span.end, b'='); + FormatTrailingComments::Comments(comments).fmt(f) + }), space(), "=>", space(), diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index 1172d4ff9e6ea..bd547345c30d1 100644 --- a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md @@ -1,4 +1,4 @@ -ts compatibility: 521/573 (90.92%) +ts compatibility: 527/573 (91.97%) # Failed @@ -19,11 +19,8 @@ ts compatibility: 521/573 (90.92%) | typescript/chain-expression/test.ts | 💥 | 0.00% | | typescript/class/empty-method-body.ts | 💥 | 80.00% | | typescript/class/quoted-property.ts | 💥 | 66.67% | -| typescript/comments/16065.ts | 💥 | 63.64% | -| typescript/comments/16207.ts | 💥 | 71.43% | -| typescript/comments/16889.ts | 💥 | 62.61% | | typescript/comments/location.ts | 💥 | 95.00% | -| typescript/comments/method_types.ts | 💥 | 79.49% | +| typescript/comments/method_types.ts | 💥 | 84.62% | | typescript/comments/type-parameters.ts | 💥 | 65.52% | | typescript/conditional-types/comments.ts | 💥✨ | 31.51% | | typescript/conditional-types/conditonal-types.ts | 💥✨ | 34.48% | @@ -33,10 +30,7 @@ ts compatibility: 521/573 (90.92%) | typescript/conditional-types/parentheses.ts | 💥✨ | 15.22% | | typescript/conformance/types/functions/functionOverloadErrorsSyntax.ts | 💥 | 0.00% | | typescript/custom/computedProperties/string.ts | 💥 | 73.33% | -| typescript/decorators/comments.ts | 💥 | 60.00% | -| typescript/decorators/decorators-comments.ts | 💥 | 65.71% | | typescript/decorators-ts/angular.ts | 💥 | 87.50% | -| typescript/decorators-ts/typeorm.ts | 💥 | 88.37% | | typescript/definite/without-annotation.ts | 💥 | 83.33% | | typescript/enum/computed-members.ts | 💥 | 0.00% | | typescript/function-type/consistent.ts | 💥 | 70.83% |