Skip to content

Commit

Permalink
Add parentheses for decorator expressions (#16458)
Browse files Browse the repository at this point in the history
Co-authored-by: fisker <[email protected]>
  • Loading branch information
y-schneider and fisker authored Jul 13, 2024
1 parent 6bbd461 commit 9102b73
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 40 deletions.
18 changes: 18 additions & 0 deletions changelog_unreleased/typescript/16458.md
Original file line number Diff line number Diff line change
@@ -0,0 1,18 @@
#### Add parentheses for decorator expressions (#16458 by @y-schneider)

Prevent parentheses around member expressions or tagged template literals from being removed to follow the stricter parsing rules of TypeScript 5.5.

<!-- prettier-ignore -->
```ts
// Input
@(foo`tagged template`)
class X {}

// Prettier stable
@foo`tagged template`
class X {}

// Prettier main
@(foo`tagged template`)
class X {}
```
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 295,7 @@
"unaries",
"uncook",
"unparenthesised",
"Unparenthesized",
"Unrestrict",
"upvoted",
"upvotes",
Expand Down
70 changes: 36 additions & 34 deletions src/language-js/needs-parens.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,40 224,10 @@ function needsParens(path, options) {
);

case "Decorator":
if (key === "expression") {
if (isMemberExpression(node) && node.computed) {
return true;
}

let hasCallExpression = false;
let hasMemberExpression = false;
let current = node;
while (current) {
switch (current.type) {
case "MemberExpression":
hasMemberExpression = true;
current = current.object;
break;
case "CallExpression":
if (
/** @(x().y) */ hasMemberExpression ||
/** @(x().y()) */ hasCallExpression
) {
return options.parser !== "typescript";
}
hasCallExpression = true;
current = current.callee;
break;
case "Identifier":
return false;
case "TaggedTemplateExpression":
// babel-parser cannot parse
// @foo`bar`
return options.parser !== "typescript";
default:
return true;
}
}
if (
key === "expression" &&
!canDecoratorExpressionUnparenthesized(node)
) {
return true;
}
break;
Expand Down Expand Up @@ -1276,4 1246,36 @@ function shouldAddParenthesesToChainElement(path) {
return false;
}

function isDecoratorMemberExpression(node) {
if (node.type === "Identifier") {
return true;
}

if (isMemberExpression(node)) {
return (
!node.computed &&
!node.optional &&
node.property.type === "Identifier" &&
isDecoratorMemberExpression(node.object)
);
}

return false;
}

// Based on babel implementation
// https://github.com/nicolo-ribaudo/babel/blob/c4b88a4e5005364255f7e964fe324cf7bfdfb019/packages/babel-generator/src/node/index.ts#L111
function canDecoratorExpressionUnparenthesized(node) {
if (node.type === "ChainExpression") {
node = node.expression;
}

return (
isDecoratorMemberExpression(node) ||
(isCallExpression(node) &&
!node.optional &&
isDecoratorMemberExpression(node.callee))
);
}

export default needsParens;
36 changes: 34 additions & 2 deletions tests/format/js/decorators/__snapshots__/format.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 176,7 @@ exports[`member-expression.js [acorn] format 1`] = `
"Unexpected character '@' (3:5)
1 | [
2 | class {
> 3 | @(decorators[0])
> 3 | @(decorator)
| ^
4 | method() {}
5 | },
Expand All @@ -188,7 188,7 @@ exports[`member-expression.js [espree] format 1`] = `
"Unexpected character '@' (3:5)
1 | [
2 | class {
> 3 | @(decorators[0])
> 3 | @(decorator)
| ^
4 | method() {}
5 | },
Expand All @@ -203,6 203,18 @@ printWidth: 80
| printWidth
=====================================input======================================
[
class {
@(decorator)
method() {}
},
class {
@(decorator())
method() {}
},
class {
@(decorator?.())
method() {}
},
class {
@(decorators[0])
method() {}
Expand All @@ -223,6 235,10 @@ printWidth: 80
@(decorators?.at(0))
method() {}
},
class {
@(decorators.at?.(0))
method() {}
},
class {
@(decorators.first)
method() {}
Expand Down Expand Up @@ -255,6 271,18 @@ printWidth: 80
=====================================output=====================================
[
class {
@decorator
method() {}
},
class {
@decorator()
method() {}
},
class {
@(decorator?.())
method() {}
},
class {
@(decorators[0])
method() {}
Expand All @@ -275,6 303,10 @@ printWidth: 80
@(decorators?.at(0))
method() {}
},
class {
@(decorators.at?.(0))
method() {}
},
class {
@decorators.first
method() {}
Expand Down
16 changes: 16 additions & 0 deletions tests/format/js/decorators/member-expression.js
Original file line number Diff line number Diff line change
@@ -1,4 1,16 @@
[
class {
@(decorator)
method() {}
},
class {
@(decorator())
method() {}
},
class {
@(decorator?.())
method() {}
},
class {
@(decorators[0])
method() {}
Expand All @@ -19,6 31,10 @@
@(decorators?.at(0))
method() {}
},
class {
@(decorators.at?.(0))
method() {}
},
class {
@(decorators.first)
method() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 301,7 @@ semi: false
class X {}
=====================================output=====================================
@test().x("global").y()
@(test().x("global").y())
class X {}
================================================================================
Expand All @@ -317,7 317,7 @@ printWidth: 80
class X {}
=====================================output=====================================
@test().x("global").y()
@(test().x("global").y())
class X {}
================================================================================
Expand All @@ -337,7 337,7 @@ class Test {
=====================================output=====================================
class Test {
@foo\`bar\`
@(foo\`bar\`)
text: string = "text"
}
Expand All @@ -357,7 357,7 @@ class Test {
=====================================output=====================================
class Test {
@foo\`bar\`
@(foo\`bar\`)
text: string = "text";
}
Expand Down

0 comments on commit 9102b73

Please sign in to comment.