Skip to content

Commit e582487

Browse files
DunqingCopilot
authored andcommitted
fix(formatter): comments in export class decorators are printing incorrectly (#15897)
* Fixes #15860 Due to #10409
1 parent 637dcd2 commit e582487

File tree

3 files changed

+139
-1
lines changed

3 files changed

+139
-1
lines changed

crates/oxc_formatter/src/ast_nodes/iterator.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//! - `impl_ast_node_vec!` - For non-Option types (uses `.map()`)
55
//! - `impl_ast_node_vec_for_option!` - For Option types (uses `.and_then()`)
66
7+
use std::cmp::min;
8+
79
use oxc_allocator::{Allocator, Vec};
810
use oxc_ast::ast::*;
911
use oxc_span::{GetSpan, Span};
@@ -158,7 +160,6 @@ impl_ast_node_vec!(ObjectPropertyKind<'a>);
158160
impl_ast_node_vec!(TemplateElement<'a>);
159161
impl_ast_node_vec!(Argument<'a>);
160162
impl_ast_node_vec!(AssignmentTargetProperty<'a>);
161-
impl_ast_node_vec!(Statement<'a>);
162163
impl_ast_node_vec!(Directive<'a>);
163164
impl_ast_node_vec!(VariableDeclarator<'a>);
164165
impl_ast_node_vec!(SwitchCase<'a>);
@@ -181,3 +182,97 @@ impl_ast_node_vec!(TSInterfaceHeritage<'a>);
181182
impl_ast_node_vec!(Decorator<'a>);
182183
impl_ast_node_vec_for_option!(Option<AssignmentTargetMaybeDefault<'a>>);
183184
impl_ast_node_vec_for_option!(Option<BindingPattern<'a>>);
185+
186+
// Manual implementation for Vec<Statement> because we have to handle span
187+
// for ExportDefaultDeclaration and ExportNamedDeclaration that may include
188+
// decorators.
189+
// <https://github.com/oxc-project/oxc/issues/10409>
190+
impl<'a> AstNode<'a, Vec<'a, Statement<'a>>> {
191+
pub fn iter(&self) -> AstNodeIterator<'a, Statement<'a>> {
192+
AstNodeIterator {
193+
inner: self.inner.iter().peekable(),
194+
parent: self.parent,
195+
allocator: self.allocator,
196+
}
197+
}
198+
pub fn first(&self) -> Option<&'a AstNode<'a, Statement<'a>>> {
199+
let mut inner_iter = self.inner.iter();
200+
self.allocator
201+
.alloc(inner_iter.next().map(|inner| AstNode {
202+
inner,
203+
parent: self.parent,
204+
allocator: self.allocator,
205+
following_span: inner_iter.next().map(GetSpan::span),
206+
}))
207+
.as_ref()
208+
}
209+
pub fn last(&self) -> Option<&'a AstNode<'a, Statement<'a>>> {
210+
self.allocator
211+
.alloc(self.inner.last().map(|inner| AstNode {
212+
inner,
213+
parent: self.parent,
214+
allocator: self.allocator,
215+
following_span: None,
216+
}))
217+
.as_ref()
218+
}
219+
}
220+
impl<'a> Iterator for AstNodeIterator<'a, Statement<'a>> {
221+
type Item = &'a AstNode<'a, Statement<'a>>;
222+
fn next(&mut self) -> Option<Self::Item> {
223+
let allocator = self.allocator;
224+
allocator
225+
.alloc(self.inner.next().map(|inner| AstNode {
226+
parent: self.parent,
227+
inner,
228+
allocator,
229+
following_span: {
230+
match self.inner.peek() {
231+
// `@decorator export default class A {}`
232+
// Get the span of the decorator.
233+
Some(Statement::ExportDefaultDeclaration(export)) => {
234+
if let ExportDefaultDeclarationKind::ClassDeclaration(class) =
235+
&export.declaration
236+
&& let Some(decorator) = class.decorators.first()
237+
{
238+
Some(Span::new(
239+
min(decorator.span.start, export.span.start),
240+
export.span.end,
241+
))
242+
} else {
243+
Some(export.span)
244+
}
245+
}
246+
// `@decorator export class A {}`
247+
// Get the span of the decorator.
248+
Some(Statement::ExportNamedDeclaration(export)) => {
249+
if let Some(Declaration::ClassDeclaration(class)) = &export.declaration
250+
&& let Some(decorator) = class.decorators.first()
251+
{
252+
Some(Span::new(
253+
min(decorator.span.start, export.span.start),
254+
export.span.end,
255+
))
256+
} else {
257+
Some(export.span)
258+
}
259+
}
260+
Some(next) => Some(next.span()),
261+
None => None,
262+
}
263+
},
264+
}))
265+
.as_ref()
266+
}
267+
}
268+
impl<'a> IntoIterator for &AstNode<'a, Vec<'a, Statement<'a>>> {
269+
type Item = &'a AstNode<'a, Statement<'a>>;
270+
type IntoIter = AstNodeIterator<'a, Statement<'a>>;
271+
fn into_iter(self) -> Self::IntoIter {
272+
AstNodeIterator::<Statement<'a>> {
273+
inner: self.inner.iter().peekable(),
274+
parent: self.parent,
275+
allocator: self.allocator,
276+
}
277+
}
278+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// test.ts
2+
import { Component } from "@angular/core";
3+
4+
@Component({
5+
selector: "my-component", // test
6+
})
7+
export class AppMyComponent {}
8+
9+
@Component({
10+
selector: "my-component", // test
11+
})
12+
export default class AppMyComponent {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
source: crates/oxc_formatter/tests/fixtures/mod.rs
3+
---
4+
==================== Input ====================
5+
// test.ts
6+
import { Component } from "@angular/core";
7+
8+
@Component({
9+
selector: "my-component", // test
10+
})
11+
export class AppMyComponent {}
12+
13+
@Component({
14+
selector: "my-component", // test
15+
})
16+
export default class AppMyComponent {}
17+
==================== Output ====================
18+
// test.ts
19+
import { Component } from "@angular/core";
20+
21+
@Component({
22+
selector: "my-component", // test
23+
})
24+
export class AppMyComponent {}
25+
26+
@Component({
27+
selector: "my-component", // test
28+
})
29+
export default class AppMyComponent {}
30+
31+
===================== End =====================

0 commit comments

Comments
 (0)