Skip to content

Commit e03ea5d

Browse files
committed
fix(formatter): missing parenthesis for expression of decorator
1 parent 833b2cd commit e03ea5d

File tree

4 files changed

+43
-38
lines changed

4 files changed

+43
-38
lines changed

crates/oxc_formatter/src/parentheses/expression.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, MemberExpression<'a>> {
213213

214214
impl<'a> NeedsParentheses<'a> for AstNode<'a, ComputedMemberExpression<'a>> {
215215
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
216-
matches!(self.parent, AstNodes::Decorator(_))
216+
false
217217
}
218218
}
219219

@@ -236,26 +236,10 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, PrivateFieldExpression<'a>> {
236236
}
237237
}
238238

239-
fn is_identifier_or_static_member_only(callee: &Expression) -> bool {
240-
let mut expr = callee;
241-
loop {
242-
match expr {
243-
Expression::Identifier(_) => return true,
244-
Expression::StaticMemberExpression(static_member) => {
245-
expr = &static_member.object;
246-
}
247-
_ => break,
248-
}
249-
}
250-
251-
false
252-
}
253-
254239
impl<'a> NeedsParentheses<'a> for AstNode<'a, CallExpression<'a>> {
255240
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
256241
match self.parent {
257242
AstNodes::NewExpression(_) => true,
258-
AstNodes::Decorator(_) => !is_identifier_or_static_member_only(&self.callee),
259243
AstNodes::ExportDefaultDeclaration(_) => {
260244
let callee = &self.callee();
261245
let callee_span = callee.span();
@@ -535,7 +519,7 @@ impl<'a> NeedsParentheses<'a> for AstNode<'a, AwaitExpression<'a>> {
535519
impl<'a> NeedsParentheses<'a> for AstNode<'a, ChainExpression<'a>> {
536520
fn needs_parentheses(&self, f: &Formatter<'_, 'a>) -> bool {
537521
match self.parent {
538-
AstNodes::NewExpression(_) | AstNodes::Decorator(_) => true,
522+
AstNodes::NewExpression(_) => true,
539523
AstNodes::CallExpression(call) => !call.optional,
540524
AstNodes::StaticMemberExpression(member) => !member.optional,
541525
AstNodes::ComputedMemberExpression(member) => {

crates/oxc_formatter/src/write/decorators.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,48 @@ impl<'a> Format<'a> for AstNode<'a, Vec<'a, Decorator<'a>>> {
5050
}
5151
}
5252

53+
fn is_identifier_or_static_member_only(callee: &Expression) -> bool {
54+
let mut expr = callee;
55+
loop {
56+
match expr {
57+
Expression::Identifier(_) => return true,
58+
Expression::StaticMemberExpression(static_member) => {
59+
expr = &static_member.object;
60+
}
61+
_ => break,
62+
}
63+
}
64+
65+
false
66+
}
67+
5368
impl<'a> FormatWrite<'a> for AstNode<'a, Decorator<'a>> {
5469
fn write(&self, f: &mut Formatter<'_, 'a>) -> FormatResult<()> {
5570
self.format_leading_comments(f)?;
56-
write!(f, ["@", self.expression()])
71+
write!(f, ["@"]);
72+
73+
// Determine if parentheses are required around decorator expressions
74+
let needs_parentheses = match &self.expression {
75+
// Identifiers: `@decorator` needs no parens
76+
Expression::Identifier(_) => false,
77+
// Call expressions: `@obj.method()` needs no parens, `@(complex().method)()` needs parens
78+
Expression::CallExpression(call) => !is_identifier_or_static_member_only(&call.callee),
79+
// Static member expressions: `@obj.prop` needs no parens, `@(complex[key])` needs parens
80+
Expression::StaticMemberExpression(static_member) => {
81+
!is_identifier_or_static_member_only(&static_member.object)
82+
}
83+
// All other expressions need parentheses: `@(a + b)`, `@(condition ? a : b)`
84+
_ => true,
85+
};
86+
87+
if needs_parentheses {
88+
write!(f, "(")?;
89+
}
90+
write!(f, [self.expression()])?;
91+
if needs_parentheses {
92+
write!(f, ")")?;
93+
}
94+
Ok(())
5795
}
5896
}
5997

tasks/coverage/snapshots/formatter_typescript.snap

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ commit: 261630d6
22

33
formatter_typescript Summary:
44
AST Parsed : 8816/8816 (100.00%)
5-
Positive Passed: 8743/8816 (99.17%)
5+
Positive Passed: 8751/8816 (99.26%)
66
Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/APISample_jsdoc.ts
77
Expected `]` but found `:`
88
Expect to Parse: tasks/coverage/typescript/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts
@@ -87,20 +87,10 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/prope
8787
Expected a semicolon or an implicit semicolon after a statement, but found none
8888
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/overrideInterfaceProperty.ts
8989
'readonly' type modifier is only permitted on array and tuple literal types.
90-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/staticAutoAccessorsWithDecorators.ts
91-
Unexpected token
9290
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/classes/propertyMemberDeclarations/staticPropertyNameConflicts.ts
9391
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
9492
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/controlFlow/controlFlowAssignmentPatternOrder.ts
9593
An implementation cannot be declared in ambient contexts.
96-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/decorators/class/decoratorChecksFunctionBodies.ts
97-
Unexpected token
98-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/decorators/legacyDecorators-contextualTypes.ts
99-
Unexpected token
100-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck59.ts
101-
Cannot use `yield` as an identifier in a generator context
102-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck61.ts
103-
Cannot use `yield` as an identifier in a generator contextUnexpected token
10494
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classDeclaration/classSuper/esDecorators-classDeclaration-classSuper.2.ts
10595
Expected `{` but found `as`
10696
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/classSuper/esDecorators-classExpression-classSuper.2.ts
@@ -109,10 +99,6 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/
10999
Decorators are not valid here.Unexpected token
110100
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/classExpression/namedEvaluation/esDecorators-classExpression-namedEvaluation.8.ts
111101
Decorators are not valid here.Unexpected token
112-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/esDecorators-contextualTypes.ts
113-
Unexpected token
114-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/esDecorators/esDecorators-decoratorExpression.2.ts
115-
Unexpected token
116102
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/asOperator/asOpEmitParens.ts
117103
Expected a semicolon or an implicit semicolon after a statement, but found none
118104
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/expressions/elementAccess/letIdentifierInElementAccess01.ts
@@ -123,8 +109,6 @@ Mismatch: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuar
123109
124110
Mismatch: tasks/coverage/typescript/tests/cases/conformance/expressions/typeGuards/typeGuardsInRightOperandOfOrOrOperator.ts
125111
126-
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/externalModules/topLevelAwait.1.ts
127-
Cannot use `await` as an identifier in an async contextUnexpected token
128112
Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/generators/yieldStatementNoAsiAfterTransform.ts
129113
Expected a semicolon or an implicit semicolon after a statement, but found none
130114
Mismatch: tasks/coverage/typescript/tests/cases/conformance/interfaces/interfacesExtendingClasses/interfaceExtendingClassWithPrivates2.ts

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ts compatibility: 350/573 (61.08%)
1+
ts compatibility: 351/573 (61.26%)
22

33
# Failed
44

@@ -115,7 +115,6 @@ ts compatibility: 350/573 (61.08%)
115115
| typescript/declare/object-type-in-declare-function.ts | 💥 | 58.82% |
116116
| typescript/declare/trailing-comma/function-rest-trailing-comma.ts | 💥💥💥 | 50.00% |
117117
| typescript/decorators/comments.ts | 💥 | 60.00% |
118-
| typescript/decorators/decorator-type-assertion.ts | 💥 | 60.00% |
119118
| typescript/decorators/decorators-comments.ts | 💥 | 65.71% |
120119
| typescript/decorators-ts/angular.ts | 💥 | 87.50% |
121120
| typescript/decorators-ts/typeorm.ts | 💥 | 88.37% |

0 commit comments

Comments
 (0)