diff --git a/crates/oxc_formatter/src/print/decorators.rs b/crates/oxc_formatter/src/print/decorators.rs index f6380f8144..0476cfd695 100644 --- a/crates/oxc_formatter/src/print/decorators.rs +++ b/crates/oxc_formatter/src/print/decorators.rs @@ -35,7 +35,7 @@ impl<'a> Format<'a, JsFormatContext<'a>> for AstNode<'_, 'a, ArenaVec<'a, Decora ); } // Parameter decorators - AstNodes::FormalParameter(_) => { + AstNodes::FormalParameter(_) | AstNodes::FormalParameterRest(_) => { write!(f, should_expand_decorators(self, f).then_some(expand_parent())); } AstNodes::ExportDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) => { diff --git a/crates/oxc_formatter/src/print/mod.rs b/crates/oxc_formatter/src/print/mod.rs index 853004ccd0..5f9ea08f80 100644 --- a/crates/oxc_formatter/src/print/mod.rs +++ b/crates/oxc_formatter/src/print/mod.rs @@ -1103,8 +1103,17 @@ impl<'a> FormatWrite<'a> for AstNode<'_, 'a, BindingPattern<'a>> { impl<'a> FormatWrite<'a> for AstNode<'_, 'a, FormalParameterRest<'a>> { fn write(&self, f: &mut JsFormatter<'_, 'a>) { - write!(f, [self.rest()]); - write!(f, self.type_annotation()); + let content = format_with(|f| { + write!(f, [self.rest()]); + write!(f, self.type_annotation()); + }); + // Decorators are laid out as on `FormalParameter`, an undecorated rest parameter stays ungrouped + let decorators = self.decorators(); + if decorators.is_empty() { + write!(f, [content]); + } else { + write!(f, [group(&decorators), group(&content)]); + } } } diff --git a/crates/oxc_formatter/tests/fixtures/ts/parameters/rest-decorators.ts b/crates/oxc_formatter/tests/fixtures/ts/parameters/rest-decorators.ts new file mode 100644 index 0000000000..e64d2a7ae4 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/parameters/rest-decorators.ts @@ -0,0 +1,20 @@ +// Decorators on a rest parameter are printed like those on other parameters +class C { m(@d ...args: unknown[]) {} } +class C2 { constructor(@Inject(TOKEN) ...deps: any[]) {} } +class C3 { m(@d x, @e ...rest) {} } +class C4 { async rest(@(await p) ...args: unknown[]) {} } +class C5 { m(@d @e ...args) {} } +class C6 { + constructor( + @Inject(VERY_LONG_TOKEN_NAME_FOR_DEPENDENCY_INJECTION) private readonly a: A, + @Inject(ANOTHER_VERY_LONG_TOKEN_NAME) ...rest: unknown[] + ) {} +} +class C7 { m(@d + ...args) {} } +// Comments around the decorator keep their place +class C8 { m(@d /* c */ ...args) {} } +class C9 { m(@d // c + ...args) {} } +class C10 { m(@d ...args // c +) {} } diff --git a/crates/oxc_formatter/tests/fixtures/ts/parameters/rest-decorators.ts.snap b/crates/oxc_formatter/tests/fixtures/ts/parameters/rest-decorators.ts.snap new file mode 100644 index 0000000000..d0aee2ebc5 --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/parameters/rest-decorators.ts.snap @@ -0,0 +1,122 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +// Decorators on a rest parameter are printed like those on other parameters +class C { m(@d ...args: unknown[]) {} } +class C2 { constructor(@Inject(TOKEN) ...deps: any[]) {} } +class C3 { m(@d x, @e ...rest) {} } +class C4 { async rest(@(await p) ...args: unknown[]) {} } +class C5 { m(@d @e ...args) {} } +class C6 { + constructor( + @Inject(VERY_LONG_TOKEN_NAME_FOR_DEPENDENCY_INJECTION) private readonly a: A, + @Inject(ANOTHER_VERY_LONG_TOKEN_NAME) ...rest: unknown[] + ) {} +} +class C7 { m(@d + ...args) {} } +// Comments around the decorator keep their place +class C8 { m(@d /* c */ ...args) {} } +class C9 { m(@d // c + ...args) {} } +class C10 { m(@d ...args // c +) {} } + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +// Decorators on a rest parameter are printed like those on other parameters +class C { + m(@d ...args: unknown[]) {} +} +class C2 { + constructor(@Inject(TOKEN) ...deps: any[]) {} +} +class C3 { + m(@d x, @e ...rest) {} +} +class C4 { + async rest(@(await p) ...args: unknown[]) {} +} +class C5 { + m(@d @e ...args) {} +} +class C6 { + constructor( + @Inject(VERY_LONG_TOKEN_NAME_FOR_DEPENDENCY_INJECTION) + private readonly a: A, + @Inject(ANOTHER_VERY_LONG_TOKEN_NAME) ...rest: unknown[] + ) {} +} +class C7 { + m( + @d + ...args + ) {} +} +// Comments around the decorator keep their place +class C8 { + m(@d /* c */ ...args) {} +} +class C9 { + m( + @d // c + ...args + ) {} +} +class C10 { + m( + @d ...args // c + ) {} +} + +------------------- +{ printWidth: 100 } +------------------- +// Decorators on a rest parameter are printed like those on other parameters +class C { + m(@d ...args: unknown[]) {} +} +class C2 { + constructor(@Inject(TOKEN) ...deps: any[]) {} +} +class C3 { + m(@d x, @e ...rest) {} +} +class C4 { + async rest(@(await p) ...args: unknown[]) {} +} +class C5 { + m(@d @e ...args) {} +} +class C6 { + constructor( + @Inject(VERY_LONG_TOKEN_NAME_FOR_DEPENDENCY_INJECTION) private readonly a: A, + @Inject(ANOTHER_VERY_LONG_TOKEN_NAME) ...rest: unknown[] + ) {} +} +class C7 { + m( + @d + ...args + ) {} +} +// Comments around the decorator keep their place +class C8 { + m(@d /* c */ ...args) {} +} +class C9 { + m( + @d // c + ...args + ) {} +} +class C10 { + m( + @d ...args // c + ) {} +} + +===================== End =====================