Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Decorators Stage 2 Parsing #587

Merged
merged 14 commits into from Jun 22, 2017
Merged
5 changes: 5 additions & 0 deletions src/index.js
Expand Up @@ -47,6 +47,11 @@ const parserClassCache: { [key: string]: Class<Parser> } = {};

/** Get a Parser class with plugins applied. */
function getParserClass(pluginsFromOptions: $ReadOnlyArray<string>): Class<Parser> {

if (pluginsFromOptions.indexOf("decorators") >= 0 && pluginsFromOptions.indexOf("decorators2") >= 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say to use Array.prototype.includes instead...... but that doesn't work on Node 4 😢

throw new Error("Cannot use decorators and decorators2 plugin together");
}

// Filter out just the plugins that have an actual mixin associated with them.
let pluginList = pluginsFromOptions.filter((p) => p === "estree" || p === "flow" || p === "jsx");

Expand Down
12 changes: 10 additions & 2 deletions src/parser/expression.js
Expand Up @@ -857,8 +857,16 @@ export default class ExpressionParser extends LValParser {
if (this.eat(tt.braceR)) break;
}

while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
if (this.match(tt.at)) {
if (this.hasPlugin("decorators2")) {
this.raise(this.state.start, "Stage 2 decorators disallow object literal property decorators");
} else {
// we needn't check if decorators (stage 0) plugin is enabled since it's checked by
// the call to this.parseDecorator
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
}
}

let prop = this.startNode(), isGenerator = false, isAsync = false, startPos, startLoc;
Expand Down
3 changes: 3 additions & 0 deletions src/parser/lval.js
Expand Up @@ -187,6 +187,9 @@ export default class LValParser extends NodeUtils {
break;
} else {
const decorators = [];
if (this.match(tt.at) && this.hasPlugin("decorators2")) {
this.raise(this.state.start, "Stage 2 decorators cannot be used to decorate parameters");
}
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
}
Expand Down
45 changes: 43 additions & 2 deletions src/parser/statement.js
Expand Up @@ -152,11 +152,18 @@ export default class StatementParser extends ExpressionParser {
takeDecorators(node: N.HasDecorators): void {
if (this.state.decorators.length) {
node.decorators = this.state.decorators;
if (this.hasPlugin("decorators2")) {
this.resetStartLocationFromNode(node, this.state.decorators[0]);
}
this.state.decorators = [];
}
}

parseDecorators(allowExport?: boolean): void {
if (this.hasPlugin("decorators2")) {
allowExport = false;
}

while (this.match(tt.at)) {
const decorator = this.parseDecorator();
this.state.decorators.push(decorator);
Expand All @@ -172,12 +179,38 @@ export default class StatementParser extends ExpressionParser {
}

parseDecorator(): N.Decorator {
if (!this.hasPlugin("decorators")) {
if (!(this.hasPlugin("decorators") || this.hasPlugin("decorators2"))) {
this.unexpected();
}

const node = this.startNode();
this.next();
node.expression = this.parseMaybeAssign();

if (this.hasPlugin("decorators2")) {
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");
}

if (this.eat(tt.parenL)) {
const node = this.startNodeAt(startPos, startLoc);
node.callee = expr;
node.arguments = this.parseCallExpressionArguments(tt.parenR, false);
expr = this.finishNode(node, "CallExpression");
this.toReferencedList(expr.arguments);
}

node.expression = expr;
} else {
node.expression = this.parseMaybeAssign();
}
return this.finishNode(node, "Decorator");
}

Expand Down Expand Up @@ -679,10 +712,17 @@ export default class StatementParser extends ExpressionParser {
// steal the decorators if there are any
if (decorators.length) {
member.decorators = decorators;
if (this.hasPlugin("decorators2")) {
this.resetStartLocationFromNode(member, decorators[0]);
}
decorators = [];
}

this.parseClassMember(classBody, member, state);

if (this.hasPlugin("decorators2") && member.kind != "method" && member.decorators && member.decorators.length > 0) {
this.raise(member.start, "Stage 2 decorators may only be used with a class or a class method");
}
}

if (decorators.length) {
Expand Down Expand Up @@ -750,6 +790,7 @@ export default class StatementParser extends ExpressionParser {
if (!methodOrProp.computed && methodOrProp.static && (methodOrProp.key.name === "prototype" || methodOrProp.key.value === "prototype")) {
this.raise(methodOrProp.key.start, "Classes may not have static property named prototype");
}

if (this.isClassMethod()) {
// a normal method
if (this.isNonstaticConstructor(method)) {
Expand Down
@@ -0,0 +1,2 @@
@foo('bar')
class Foo {}
@@ -0,0 +1,154 @@
{
"type": "File",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 12
}
},
"program": {
"type": "Program",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 12
}
},
"sourceType": "script",
"body": [
{
"type": "ClassDeclaration",
"start": 0,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 2,
"column": 12
}
},
"decorators": [
{
"type": "Decorator",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 11
}
},
"expression": {
"type": "CallExpression",
"start": 1,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 11
}
},
"callee": {
"type": "Identifier",
"start": 1,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 4
},
"identifierName": "foo"
},
"name": "foo"
},
"arguments": [
{
"type": "StringLiteral",
"start": 5,
"end": 10,
"loc": {
"start": {
"line": 1,
"column": 5
},
"end": {
"line": 1,
"column": 10
}
},
"extra": {
"rawValue": "bar",
"raw": "'bar'"
},
"value": "bar"
}
]
}
}
],
"id": {
"type": "Identifier",
"start": 18,
"end": 21,
"loc": {
"start": {
"line": 2,
"column": 6
},
"end": {
"line": 2,
"column": 9
},
"identifierName": "Foo"
},
"name": "Foo"
},
"superClass": null,
"body": {
"type": "ClassBody",
"start": 22,
"end": 24,
"loc": {
"start": {
"line": 2,
"column": 10
},
"end": {
"line": 2,
"column": 12
}
},
"body": []
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
@abc
class Foo {

}