Skip to content

Commit 9241980

Browse files
committed
fix(formatter): correct printing comments around decorators
1 parent 2604b28 commit 9241980

File tree

5 files changed

+86
-46
lines changed

5 files changed

+86
-46
lines changed

crates/oxc_formatter/src/write/call_arguments.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ impl<'a> Format<'a> for AstNode<'a, ArenaVec<'a, Argument<'a>>> {
9898

9999
let has_empty_line =
100100
self.iter().any(|arg| f.source_text().get_lines_before(arg.span(), f.comments()) > 1);
101-
if has_empty_line || is_function_composition_args(self) {
101+
if has_empty_line
102+
|| (!matches!(self.parent.parent(), AstNodes::Decorator(_))
103+
&& is_function_composition_args(self))
104+
{
102105
return format_all_args_broken_out(self, true, f);
103106
}
104107

crates/oxc_formatter/src/write/class.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ impl<'a> FormatWrite<'a> for AstNode<'a, MethodDefinition<'a>> {
6060
// Write modifiers in the correct order:
6161
// decorators -> accessibility -> static -> abstract -> override -> async -> generator
6262
write!(f, [self.decorators()])?;
63-
if let Some(accessibility) = &self.accessibility() {
63+
64+
if let Some(accessibility) = &self.accessibility {
6465
write!(f, [accessibility.as_str(), space()])?;
6566
}
6667
if self.r#static {
@@ -157,11 +158,6 @@ impl<'a> FormatWrite<'a> for AstNode<'a, StaticBlock<'a>> {
157158
impl<'a> FormatWrite<'a> for AstNode<'a, AccessorProperty<'a>> {
158159
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
159160
write!(f, self.decorators())?;
160-
// Handle comments between decorators and the 'accessor' keyword
161-
// We look for comments before the first character 'a' of 'accessor'
162-
// This ensures proper placement of comments like: @decorator /* comment */ accessor x
163-
let comments = f.context().comments().comments_before_character(self.span.start, b'a');
164-
FormatLeadingComments::Comments(comments).fmt(f)?;
165161

166162
if let Some(accessibility) = self.accessibility() {
167163
write!(f, [accessibility.as_str(), space()])?;

crates/oxc_formatter/src/write/decorators.rs

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use oxc_span::GetSpan;
44

55
use crate::{
66
Format, FormatResult, format_args,
7-
formatter::{Formatter, prelude::*},
7+
formatter::{
8+
Formatter,
9+
prelude::*,
10+
trivia::{FormatLeadingComments, FormatTrailingComments},
11+
},
812
generated::ast_nodes::{AstNode, AstNodes},
913
write,
1014
};
@@ -13,40 +17,46 @@ use super::FormatWrite;
1317

1418
impl<'a> Format<'a> for AstNode<'a, Vec<'a, Decorator<'a>>> {
1519
fn fmt(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
16-
if self.is_empty() {
20+
let Some(last) = self.last() else {
1721
return Ok(());
18-
}
22+
};
1923

20-
// Check parent to determine formatting context
21-
match self.parent {
22-
AstNodes::PropertyDefinition(_)
23-
| AstNodes::MethodDefinition(_)
24-
| AstNodes::AccessorProperty(_) => {
25-
return write!(
26-
f,
27-
[group(&format_args!(
28-
format_once(|f| {
29-
f.join_nodes_with_soft_line().entries(self.iter()).finish()
30-
}),
31-
soft_line_break_or_space()
32-
))
33-
.should_expand(should_expand_decorators(self, f))]
34-
);
35-
}
36-
// Parameter decorators
37-
AstNodes::FormalParameter(_) => {
38-
write!(f, should_expand_decorators(self, f).then_some(expand_parent()))?;
39-
}
40-
AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => {
41-
write!(f, [hard_line_break()])?;
42-
}
43-
_ => {
44-
write!(f, [expand_parent()])?;
24+
let format_decorators = format_once(|f| {
25+
// Check parent to determine formatting context
26+
match self.parent {
27+
AstNodes::PropertyDefinition(_)
28+
| AstNodes::MethodDefinition(_)
29+
| AstNodes::AccessorProperty(_) => {
30+
return write!(
31+
f,
32+
[group(&format_args!(
33+
format_once(|f| {
34+
f.join_nodes_with_soft_line().entries(self.iter()).finish()
35+
}),
36+
soft_line_break_or_space()
37+
))
38+
.should_expand(should_expand_decorators(self, f))]
39+
);
40+
}
41+
// Parameter decorators
42+
AstNodes::FormalParameter(_) => {
43+
write!(f, should_expand_decorators(self, f).then_some(expand_parent()))?;
44+
}
45+
AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => {
46+
write!(f, [hard_line_break()])?;
47+
}
48+
_ => {
49+
write!(f, [expand_parent()])?;
50+
}
4551
}
46-
}
4752

48-
f.join_with(&soft_line_break_or_space()).entries(self.iter()).finish()?;
49-
write!(f, [soft_line_break_or_space()])
53+
f.join_with(&soft_line_break_or_space()).entries(self.iter()).finish()?;
54+
55+
write!(f, [soft_line_break_or_space()])
56+
});
57+
58+
format_decorators.fmt(f)?;
59+
format_trailing_comments_for_last_decorator(last.span.end, f)
5060
}
5161
}
5262

@@ -103,3 +113,32 @@ fn should_expand_decorators<'a>(
103113
) -> bool {
104114
decorators.iter().any(|decorator| f.source_text().lines_after(decorator.span().end) > 0)
105115
}
116+
117+
pub fn format_trailing_comments_for_last_decorator(
118+
mut start: u32,
119+
f: &mut Formatter<'_, '_>,
120+
) -> FormatResult<()> {
121+
let mut comments = f.context().comments().unprinted_comments();
122+
123+
for (i, comment) in comments.iter().enumerate() {
124+
if !f.source_text().all_bytes_match(start, comment.span.start, |b| b.is_ascii_whitespace())
125+
{
126+
comments = &comments[..i];
127+
break;
128+
}
129+
130+
start = comment.span.end;
131+
}
132+
133+
if !comments.is_empty() {
134+
write!(
135+
f,
136+
[group(&format_args!(
137+
FormatTrailingComments::Comments(comments),
138+
soft_line_break_or_space()
139+
))]
140+
)?;
141+
}
142+
143+
Ok(())
144+
}

crates/oxc_formatter/src/write/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,6 +1725,9 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSFunctionType<'a>> {
17251725
write!(f, params)?;
17261726
}
17271727

1728+
let comments = f.context().comments().comments_before_character(params.span.end, b'=');
1729+
FormatTrailingComments::Comments(comments).fmt(f)?;
1730+
17281731
write!(f, [space(), "=>", space(), return_type.type_annotation()])
17291732
});
17301733

@@ -1756,6 +1759,11 @@ impl<'a> FormatWrite<'a> for AstNode<'a, TSConstructorType<'a>> {
17561759
space(),
17571760
type_parameters,
17581761
params,
1762+
format_once(|f| {
1763+
let comments =
1764+
f.context().comments().comments_before_character(params.span.end, b'=');
1765+
FormatTrailingComments::Comments(comments).fmt(f)
1766+
}),
17591767
space(),
17601768
"=>",
17611769
space(),

tasks/prettier_conformance/snapshots/prettier.ts.snap.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ts compatibility: 521/573 (90.92%)
1+
ts compatibility: 527/573 (91.97%)
22

33
# Failed
44

@@ -19,11 +19,8 @@ ts compatibility: 521/573 (90.92%)
1919
| typescript/chain-expression/test.ts | 💥 | 0.00% |
2020
| typescript/class/empty-method-body.ts | 💥 | 80.00% |
2121
| typescript/class/quoted-property.ts | 💥 | 66.67% |
22-
| typescript/comments/16065.ts | 💥 | 63.64% |
23-
| typescript/comments/16207.ts | 💥 | 71.43% |
24-
| typescript/comments/16889.ts | 💥 | 62.61% |
2522
| typescript/comments/location.ts | 💥 | 95.00% |
26-
| typescript/comments/method_types.ts | 💥 | 79.49% |
23+
| typescript/comments/method_types.ts | 💥 | 84.62% |
2724
| typescript/comments/type-parameters.ts | 💥 | 65.52% |
2825
| typescript/conditional-types/comments.ts | 💥✨ | 31.51% |
2926
| typescript/conditional-types/conditonal-types.ts | 💥✨ | 34.48% |
@@ -33,10 +30,7 @@ ts compatibility: 521/573 (90.92%)
3330
| typescript/conditional-types/parentheses.ts | 💥✨ | 15.22% |
3431
| typescript/conformance/types/functions/functionOverloadErrorsSyntax.ts | 💥 | 0.00% |
3532
| typescript/custom/computedProperties/string.ts | 💥 | 73.33% |
36-
| typescript/decorators/comments.ts | 💥 | 60.00% |
37-
| typescript/decorators/decorators-comments.ts | 💥 | 65.71% |
3833
| typescript/decorators-ts/angular.ts | 💥 | 87.50% |
39-
| typescript/decorators-ts/typeorm.ts | 💥 | 88.37% |
4034
| typescript/definite/without-annotation.ts | 💥 | 83.33% |
4135
| typescript/enum/computed-members.ts | 💥 | 0.00% |
4236
| typescript/function-type/consistent.ts | 💥 | 70.83% |

0 commit comments

Comments
 (0)