diff --git a/crates/oxc_formatter/src/parentheses/expression.rs b/crates/oxc_formatter/src/parentheses/expression.rs index 4dc090bb2aa12..c42440332f15d 100644 --- a/crates/oxc_formatter/src/parentheses/expression.rs +++ b/crates/oxc_formatter/src/parentheses/expression.rs @@ -213,7 +213,7 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, MemberExpression<'a>> { impl<'a> NeedsParentheses<'a> for AstNode<'a, ComputedMemberExpression<'a>> { fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { - matches!(self.parent, AstNodes::Decorator(_)) + false } } @@ -236,26 +236,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, PrivateFieldExpression<'a>> { } } -fn is_identifier_or_static_member_only(callee: &Expression) -> bool { - let mut expr = callee; - loop { - match expr { - Expression::Identifier(_) => return true, - Expression::StaticMemberExpression(static_member) => { - expr = &static_member.object; - } - _ => break, - } - } - - false -} - impl<'a> NeedsParentheses<'a> for AstNode<'a, CallExpression<'a>> { fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { match self.parent { AstNodes::NewExpression(_) => true, - AstNodes::Decorator(_) => !is_identifier_or_static_member_only(&self.callee), AstNodes::ExportDefaultDeclaration(_) => { let callee = &self.callee(); let callee_span = callee.span(); @@ -535,7 +519,7 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, AwaitExpression<'a>> { impl<'a> NeedsParentheses<'a> for AstNode<'a, ChainExpression<'a>> { fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool { match self.parent { - AstNodes::NewExpression(_) | AstNodes::Decorator(_) => true, + AstNodes::NewExpression(_) => true, AstNodes::CallExpression(call) => !call.optional, AstNodes::StaticMemberExpression(member) => !member.optional, AstNodes::ComputedMemberExpression(member) => { diff --git a/crates/oxc_formatter/src/write/decorators.rs b/crates/oxc_formatter/src/write/decorators.rs index 0cc0125d69c31..2f8d6f60788eb 100644 --- a/crates/oxc_formatter/src/write/decorators.rs +++ b/crates/oxc_formatter/src/write/decorators.rs @@ -50,10 +50,48 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, Decorator<'a>>> { } } +fn is_identifier_or_static_member_only(callee: &Expression) -> bool { + let mut expr = callee; + loop { + match expr { + Expression::Identifier(_) => return true, + Expression::StaticMemberExpression(static_member) => { + expr = &static_member.object; + } + _ => break, + } + } + + false +} + impl<'a> FormatWrite<'a> for AstNode<'a, Decorator<'a>> { fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> { self.format_leading_comments(f)?; - write!(f, ["@", self.expression()]) + write!(f, ["@"]); + + // Determine if parentheses are required around decorator expressions + let needs_parentheses = match &self.expression { + // Identifiers: `@decorator` needs no parens + Expression::Identifier(_) => false, + // Call expressions: `@obj.method()` needs no parens, `@(complex().method)()` needs parens + Expression::CallExpression(call) => !is_identifier_or_static_member_only(&call.callee), + // Static member expressions: `@obj.prop` needs no parens, `@(complex[key])` needs parens + Expression::StaticMemberExpression(static_member) => { + !is_identifier_or_static_member_only(&static_member.object) + } + // All other expressions need parentheses: `@(a + b)`, `@(condition ? a : b)` + _ => true, + }; + + if needs_parentheses { + write!(f, "(")?; + } + write!(f, [self.expression()])?; + if needs_parentheses { + write!(f, ")")?; + } + Ok(()) } } diff --git a/tasks/coverage/snapshots/formatter_typescript.snap b/tasks/coverage/snapshots/formatter_typescript.snap index 77c9382da375f..8799896979061 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: 8755/8816 (99.31%) +Positive Passed: 8763/8816 (99.40%) Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/APISample_jsdoc.ts Expected `]` but found `:` Mismatch: tasks/coverage/typescript/tests/cases/compiler/amdLikeInputDeclarationEmit.ts @@ -65,20 +65,10 @@ Mismatch: tasks/coverage/typescript/tests/cases/compiler/unionSignaturesWithThis Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/overrideInterfaceProperty.ts 'readonly' type modifier is only permitted on array and tuple literal types. -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/staticAutoAccessorsWithDecorators.ts -Unexpected token Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/staticPropertyNameConflicts.ts 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/decorators/class/decoratorChecksFunctionBodies.ts -Unexpected token -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/decorators/legacyDecorators-contextualTypes.ts -Unexpected token -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck59.ts -Cannot use `yield` as an identifier in a generator context -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck61.ts -Cannot use `yield` as an identifier in a generator contextUnexpected token Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classDeclaration/classSuper/esDecorators-classDeclaration-classSuper.2.ts Expected `{` but found `as` Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/classSuper/esDecorators-classExpression-classSuper.2.ts @@ -87,10 +77,6 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/ 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/esDecorators/esDecorators-contextualTypes.ts -Unexpected token -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/esDecorators-decoratorExpression.2.ts -Unexpected token Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/asOperator/asOpEmitParens.ts Expected a semicolon or an implicit semicolon after a statement, but found none Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/elementAccess/letIdentifierInElementAccess01.ts @@ -101,8 +87,6 @@ Mismatch: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuar Mismatch: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardsInRightOperandOfOrOrOperator.ts -Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/externalModules/topLevelAwait.1.ts -Cannot use `await` as an identifier in an async contextUnexpected token Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts Expected a semicolon or an implicit semicolon after a statement, but found none Mismatch: tasks/coverage/typescript/tests/cases/conformance/interfaces/interfacesExtendingClasses/interfaceExtendingClassWithPrivates2.ts diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index faf3bd4f81eb7..323d60f8a817b 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: 351/573 (61.26%) +ts compatibility: 352/573 (61.43%) # Failed @@ -115,7 +115,6 @@ ts compatibility: 351/573 (61.26%) | typescript/declare/object-type-in-declare-function.ts | 💥 | 58.82% | | typescript/declare/trailing-comma/function-rest-trailing-comma.ts | 💥💥💥 | 50.00% | | typescript/decorators/comments.ts | 💥 | 60.00% | -| typescript/decorators/decorator-type-assertion.ts | 💥 | 60.00% | | typescript/decorators/decorators-comments.ts | 💥 | 65.71% | | typescript/decorators-ts/angular.ts | 💥 | 87.50% | | typescript/decorators-ts/typeorm.ts | 💥 | 88.37% |