Add parentheses for decorator expressions (#16458)

Co-authored-by: fisker <lionkay@gmail.com>
This commit is contained in:
y-schneiderandfisker authored and GitHub committed 2024-07-13 21:15:40 +09:00
1 parent 6bbd46194e
commit 9102b73d8c
6 files changed
+109 -40

No files matched your search

+18
View File
@@ -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
View File
@@ -295,6 +295,7 @@
"unaries",
"uncook",
"unparenthesised",
"Unparenthesized",
"Unrestrict",
"upvoted",
"upvotes",
+36 -34
View File
@@ -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;
@@ -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;
@@ -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 | },
@@ -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 | },
@@ -203,6 +203,18 @@ printWidth: 80
| printWidth
=====================================input======================================
[
class {
@(decorator)
method() {}
},
class {
@(decorator())
method() {}
},
class {
@(decorator?.())
method() {}
},
class {
@(decorators[0])
method() {}
@@ -223,6 +235,10 @@ printWidth: 80
@(decorators?.at(0))
method() {}
},
class {
@(decorators.at?.(0))
method() {}
},
class {
@(decorators.first)
method() {}
@@ -255,6 +271,18 @@ printWidth: 80
=====================================output=====================================
[
class {
@decorator
method() {}
},
class {
@decorator()
method() {}
},
class {
@(decorator?.())
method() {}
},
class {
@(decorators[0])
method() {}
@@ -275,6 +303,10 @@ printWidth: 80
@(decorators?.at(0))
method() {}
},
class {
@(decorators.at?.(0))
method() {}
},
class {
@decorators.first
method() {}
@@ -1,4 +1,16 @@
[
class {
@(decorator)
method() {}
},
class {
@(decorator())
method() {}
},
class {
@(decorator?.())
method() {}
},
class {
@(decorators[0])
method() {}
@@ -19,6 +31,10 @@
@(decorators?.at(0))
method() {}
},
class {
@(decorators.at?.(0))
method() {}
},
class {
@(decorators.first)
method() {}
@@ -301,7 +301,7 @@ semi: false
class X {}
=====================================output=====================================
@test().x("global").y()
@(test().x("global").y())
class X {}
================================================================================
@@ -317,7 +317,7 @@ printWidth: 80
class X {}
=====================================output=====================================
@test().x("global").y()
@(test().x("global").y())
class X {}
================================================================================
@@ -337,7 +337,7 @@ class Test {
=====================================output=====================================
class Test {
@foo\`bar\`
@(foo\`bar\`)
text: string = "text"
}
@@ -357,7 +357,7 @@ class Test {
=====================================output=====================================
class Test {
@foo\`bar\`
@(foo\`bar\`)
text: string = "text";
}