From 5c3645bca30758f07bd4abcb004ae4598d3966c7 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:46:51 +0000 Subject: [PATCH] fix(formatter): handle decorators correctly for class expressions in export (#13845) The decorators of class expressions should be printed on the class expression itself; otherwise, the parentheses wouldn't wrap the decorators, which would cause a syntax error. Input: ```ts export default (@dec class {}) {}; ``` Output before: ```ts export default @dec ( class {} ) { } ``` Output after: ```ts export default (@dec class {}) {} --- crates/oxc_formatter/src/write/class.rs | 10 ++++++---- crates/oxc_formatter/src/write/export_declarations.rs | 1 + tasks/coverage/snapshots/formatter_typescript.snap | 6 +----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/oxc_formatter/src/write/class.rs b/crates/oxc_formatter/src/write/class.rs index 830b41ea16e81..e4a9887d2ca9b 100644 --- a/crates/oxc_formatter/src/write/class.rs +++ b/crates/oxc_formatter/src/write/class.rs @@ -259,10 +259,12 @@ impl<'a> Format<'a> for FormatClass<'a, '_> { // Decorators are handled differently depending on the parent context // When the class is exported, the export statement handles decorator formatting // to ensure proper placement relative to the export keyword - if !matches!( - self.parent, - AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) - ) { + if self.is_expression() + || !matches!( + self.parent, + AstNodes::ExportNamedDeclaration(_) | AstNodes::ExportDefaultDeclaration(_) + ) + { write!(f, decorators)?; } diff --git a/crates/oxc_formatter/src/write/export_declarations.rs b/crates/oxc_formatter/src/write/export_declarations.rs index 2da6f208c99e5..24c25811f7615 100644 --- a/crates/oxc_formatter/src/write/export_declarations.rs +++ b/crates/oxc_formatter/src/write/export_declarations.rs @@ -33,6 +33,7 @@ fn format_export_keyword_with_class_decorators<'a>( if let AstNodes::Class(class) = declaration && !class.decorators.is_empty() + && !class.is_expression() { // `@decorator export class Cls {}` // decorators are placed before the export keyword diff --git a/tasks/coverage/snapshots/formatter_typescript.snap b/tasks/coverage/snapshots/formatter_typescript.snap index 0eb719daaf9f9..09b74d0b9fc1c 100644 --- a/tasks/coverage/snapshots/formatter_typescript.snap +++ b/tasks/coverage/snapshots/formatter_typescript.snap @@ -2,7 +2,7 @@ commit: 261630d6 formatter_typescript Summary: AST Parsed : 8816/8816 (100.00%) -Positive Passed: 8785/8816 (99.65%) +Positive Passed: 8787/8816 (99.67%) Mismatch: tasks/coverage/typescript/tests/cases/compiler/amdLikeInputDeclarationEmit.ts Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/arrayFromAsync.ts @@ -45,10 +45,6 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/prope Classes may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototypeClasses may not have a static property named prototype Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts An implementation cannot be declared in ambient contexts. -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/esDecorators-classExpression-missingEmitHelpers-classDecorator.3.ts -Decorators are not valid here.Unexpected token -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/namedEvaluation/esDecorators-classExpression-namedEvaluation.8.ts -Decorators are not valid here.Unexpected token Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/elementAccess/letIdentifierInElementAccess01.ts Unexpected token Mismatch: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardsInRightOperandOfAndAndOperator.ts