Skip to content

Commit

Permalink
Parenthesize non-simple decorator expression (#14378)
Browse files Browse the repository at this point in the history
  • Loading branch information
JLHwung committed Mar 22, 2022
1 parent 71a003a commit c79709a
Show file tree
Hide file tree
Showing 28 changed files with 560 additions and 25 deletions.
38 changes: 37 additions & 1 deletion packages/babel-generator/src/generators/expressions.ts
Expand Up @@ -113,9 +113,45 @@ export function Super(this: Printer) {
this.word("super");
}

function isDecoratorMemberExpression(
node: t.Expression | t.V8IntrinsicIdentifier,
) {
switch (node.type) {
case "Identifier":
return true;
case "MemberExpression":
return (
!node.computed &&
node.property.type === "Identifier" &&
isDecoratorMemberExpression(node.object)
);
default:
return false;
}
}
function shouldParenthesizeDecoratorExpression(
node: t.Expression | t.V8IntrinsicIdentifier,
) {
if (node.type === "CallExpression") {
node = node.callee;
}
if (node.type === "ParenthesizedExpression") {
// We didn't check extra?.parenthesized here because we don't track decorators in needsParen
return false;
}
return !isDecoratorMemberExpression(node);
}

export function Decorator(this: Printer, node: t.Decorator) {
this.token("@");
this.print(node.expression, node);
const { expression } = node;
if (shouldParenthesizeDecoratorExpression(expression)) {
this.token("(");
this.print(expression, node);
this.token(")");
} else {
this.print(expression, node);
}
this.newline();
}

Expand Down
@@ -1 +1,5 @@
{ "plugins": ["classProperties", "decorators-legacy", "typescript"], "decoratorsBeforeExport": true, "retainLines": true }
{
"plugins": ["decorators-legacy", "typescript"],
"decoratorsBeforeExport": true,
"retainLines": true
}
@@ -0,0 +1,6 @@
class C {
@dec(0)
@globalThis.dec(0)
@(globalThis["dec"])(0)
p
}
@@ -0,0 +1,6 @@
class C {
@dec(0)
@globalThis.dec(0)
@(globalThis["dec"](0))
p;
}
@@ -0,0 +1,6 @@
class C {
@dec
@globalThis.dec
@globalThis.self.dec
p
}
@@ -0,0 +1,6 @@
class C {
@dec
@globalThis.dec
@globalThis.self.dec
p;
}
@@ -0,0 +1,30 @@
class C extends class {} {
#x;
constructor() {
class ShouldPreserveParens {
@(decs[0])
@(decs`1`)
@(this?.two)
@(self.#x)
@(this.dec)
@(super.dec)
@(new DecFactory)
@(decs[three])()
p;
}

class ShouldNotAddParens {
@decs
@decs.one
@decs.two()
p;
}

class WillPreserveParens {
@(decs)
@(decs.one)
@(decs.two())
p;
}
}
}
@@ -0,0 +1,5 @@
{
"plugins": [["decorators", { "decoratorsBeforeExport": false }]],
"decoratorsBeforeExport": true,
"parserOpts": { "createParenthesizedExpressions": true }
}
@@ -0,0 +1,32 @@
class C extends class {} {
#x;

constructor() {
class ShouldPreserveParens {
@(decs[0])
@(decs`1`)
@(this?.two)
@(self.#x)
@(this.dec)
@(super.dec)
@(new DecFactory())
@(decs[three])()
p;
}

class ShouldNotAddParens {
@decs
@decs.one
@decs.two()
p;
}

class WillPreserveParens {
@(decs)
@(decs.one)
@(decs.two())
p;
}
}

}
@@ -0,0 +1,34 @@
class C extends class {} {
#x;
constructor() {
class ShouldPreserveParens {
@(decs[0])
@(decs`1`)
@(this?.two)
@(self.#x)
@(this.dec)
@(super.dec)
@(new DecFactory)
p;
}

class ShouldNotAddParens {
@decs
@decs.one
@decs.two()
p;
}

class ShouldAddParens {
@(decs[three])()
p;
}

class ShouldRemoveParens {
@(decs)
@(decs.one)
@(decs.two())
p;
}
}
}
@@ -0,0 +1,36 @@
class C extends class {} {
#x;

constructor() {
class ShouldPreserveParens {
@(decs[0])
@(decs`1`)
@(this?.two)
@(self.#x)
@(this.dec)
@(super.dec)
@(new DecFactory())
p;
}

class ShouldNotAddParens {
@decs
@decs.one
@decs.two()
p;
}

class ShouldAddParens {
@(decs[three]())
p;
}

class ShouldRemoveParens {
@decs
@decs.one
@decs.two()
p;
}
}

}
@@ -1,9 +1,4 @@
{
"plugins": [
["decorators", { "decoratorsBeforeExport": false }],
"classProperties",
"classPrivateProperties",
"classPrivateMethods"
],
"plugins": [["decorators", { "decoratorsBeforeExport": false }]],
"decoratorsBeforeExport": true
}
18 changes: 13 additions & 5 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -1770,21 +1770,29 @@ export default class ExpressionParser extends LValParser {
val = exprList[0];
}

return this.wrapParenthesis(startPos, startLoc, val);
}

wrapParenthesis(
startPos: number,
startLoc: Position,
expression: N.Expression,
): N.Expression {
if (!this.options.createParenthesizedExpressions) {
this.addExtra(val, "parenthesized", true);
this.addExtra(val, "parenStart", startPos);
this.addExtra(expression, "parenthesized", true);
this.addExtra(expression, "parenStart", startPos);

this.takeSurroundingComments(
val,
expression,
startPos,
this.state.lastTokEndLoc.index,
);

return val;
return expression;
}

const parenExpression = this.startNodeAt(startPos, startLoc);
parenExpression.expression = val;
parenExpression.expression = expression;
this.finishNode(parenExpression, "ParenthesizedExpression");
return parenExpression;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/babel-parser/src/parser/statement.js
Expand Up @@ -539,9 +539,13 @@ export default class StatementParser extends ExpressionParser {
const startLoc = this.state.startLoc;
let expr: N.Expression;

if (this.eat(tt.parenL)) {
if (this.match(tt.parenL)) {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
this.next(); // eat '('
expr = this.parseExpression();
this.expect(tt.parenR);
expr = this.wrapParenthesis(startPos, startLoc, expr);
} else {
expr = this.parseIdentifier(false);

Expand Down
@@ -0,0 +1,3 @@
class Foo {
@() => () => "bar" p;
}
@@ -0,0 +1,3 @@
{
"throws": "Unexpected token (2:4)"
}
Expand Up @@ -56,7 +56,11 @@
}
}
}
]
],
"extra": {
"parenthesized": true,
"parenStart": 1
}
}
}
],
Expand Down
Expand Up @@ -81,6 +81,10 @@
}
}
]
},
"extra": {
"parenthesized": true,
"parenStart": 14
}
}
}
Expand Down
@@ -0,0 +1,8 @@
@(foo().bar)
class Foo {
@(member[expression]) method() {}

@(foo + bar) method2() {}

@(this.foo)(bar) method3() {}
}
@@ -0,0 +1,4 @@
{
"plugins": [["decorators", { "decoratorsBeforeExport": false }]],
"createParenthesizedExpressions": true
}

0 comments on commit c79709a

Please sign in to comment.