Update decorators parsing (#7719)

* Update decorators parsing

This commit introduces three changes:
1) Class properties can be decorated
2) Decorators can contain arbitrary expressions, using @(...)
3) The Decorator node type has a new property, "arguments". This
    makes it possible do distinguish @dec() and @(dec()), which have
    different behaviors because @(dec()) is equivalent to @(dec())().

* Rename Decorator#expression to Decorator#callee

* Add test for @dec()()
This commit is contained in:
Nicolò Ribaudo
2018-04-17 23:22:03 +02:00
committed by Brian Ng
parent 81149a5cc9
commit 341bdab90c
72 changed files with 2172 additions and 505 deletions

View File

@@ -265,33 +265,37 @@ export default class StatementParser extends ExpressionParser {
this.next();
if (this.hasPlugin("decorators2")) {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
let expr = this.parseIdentifier(false);
// Every time a decorator class expression is evaluated, a new empty array is pushed onto the stack
// So that the decorators of any nested class expressions will be dealt with separately
this.state.decoratorStack.push([]);
while (this.eat(tt.dot)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = expr;
node.property = this.parseIdentifier(true);
node.computed = false;
expr = this.finishNode(node, "MemberExpression");
if (this.eat(tt.parenL)) {
node.callee = this.parseExpression();
this.expect(tt.parenR);
} else {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
let expr = this.parseIdentifier(false);
while (this.eat(tt.dot)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = expr;
node.property = this.parseIdentifier(true);
node.computed = false;
expr = this.finishNode(node, "MemberExpression");
}
node.callee = expr;
}
if (this.eat(tt.parenL)) {
const node = this.startNodeAt(startPos, startLoc);
node.callee = expr;
// Every time a decorator class expression is evaluated, a new empty array is pushed onto the stack
// So that the decorators of any nested class expressions will be dealt with separately
this.state.decoratorStack.push([]);
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
this.state.decoratorStack.pop();
expr = this.finishNode(node, "CallExpression");
this.toReferencedList(expr.arguments);
this.toReferencedList(node.arguments);
}
node.expression = expr;
this.state.decoratorStack.pop();
} else {
node.expression = this.parseMaybeAssign();
node.callee = this.parseMaybeAssign();
}
return this.finishNode(node, "Decorator");
}
@@ -959,14 +963,13 @@ export default class StatementParser extends ExpressionParser {
this.parseClassMember(classBody, member, state);
if (
this.hasPlugin("decorators2") &&
["method", "get", "set"].indexOf(member.kind) === -1 &&
member.kind === "constructor" &&
member.decorators &&
member.decorators.length > 0
) {
this.raise(
member.start,
"Stage 2 decorators may only be used with a class or a class method",
"Decorators can't be used with a constructor. Did you mean '@dec class { ... }'?",
);
}
}

View File

@@ -336,6 +336,7 @@ export type VariableDeclarator = NodeBase & {
export type Decorator = NodeBase & {
type: "Decorator",
expression: Expression,
arguments?: Array<Expression | SpreadElement>,
};
export type Directive = NodeBase & {