diff --git a/src/parser/statement.js b/src/parser/statement.js index 5f40ddb277..b3d7e1a28b 100644 --- a/src/parser/statement.js +++ b/src/parser/statement.js @@ -150,12 +150,13 @@ export default class StatementParser extends ExpressionParser { } takeDecorators(node: N.HasDecorators): void { - if (this.state.decorators.length) { - node.decorators = this.state.decorators; + const decorators = this.state.decoratorStack[this.state.decoratorStack.length - 1]; + if (decorators.length) { + node.decorators = decorators; if (this.hasPlugin("decorators2")) { - this.resetStartLocationFromNode(node, this.state.decorators[0]); + this.resetStartLocationFromNode(node, decorators[0]); } - this.state.decorators = []; + this.state.decoratorStack[this.state.decoratorStack.length - 1] = []; } } @@ -164,9 +165,10 @@ export default class StatementParser extends ExpressionParser { allowExport = false; } + const currentContextDecorators = this.state.decoratorStack[this.state.decoratorStack.length - 1]; while (this.match(tt.at)) { const decorator = this.parseDecorator(); - this.state.decorators.push(decorator); + currentContextDecorators.push(decorator); } if (allowExport && this.match(tt._export)) { @@ -207,7 +209,11 @@ export default class StatementParser extends ExpressionParser { 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); } @@ -1043,7 +1049,8 @@ export default class StatementParser extends ExpressionParser { } } - if (this.state.decorators.length) { + const currentContextDecorators = this.state.decoratorStack[this.state.decoratorStack.length - 1]; + if (currentContextDecorators.length) { const isClass = node.declaration && (node.declaration.type === "ClassDeclaration" || node.declaration.type === "ClassExpression"); if (!node.declaration || !isClass) { throw this.raise(node.start, "You can only use decorators on an export when exporting a class"); diff --git a/src/tokenizer/state.js b/src/tokenizer/state.js index 0a16182af8..31512c12f6 100644 --- a/src/tokenizer/state.js +++ b/src/tokenizer/state.js @@ -31,7 +31,7 @@ export default class State { this.labels = []; - this.decorators = []; + this.decoratorStack = [[]]; this.tokens = []; @@ -89,8 +89,9 @@ export default class State { // Labels in scope. labels: Array<{ kind: ?("loop" | "switch"), statementStart?: number }>; - // Leading decorators. - decorators: Array; + // Leading decorators. Last element of the stack represents the decorators in current context. + // Supports nesting of decorators, e.g. @foo(@bar class {}) class {} + decoratorStack: Array>; // Token store. tokens: Array; diff --git a/test/fixtures/experimental/decorators-2/decoratorception-class/actual.js b/test/fixtures/experimental/decorators-2/decoratorception-class/actual.js new file mode 100644 index 0000000000..c2f38e56ba --- /dev/null +++ b/test/fixtures/experimental/decorators-2/decoratorception-class/actual.js @@ -0,0 +1,6 @@ +@outer({ + store: @inner class Foo {} +}) +class Bar { + +} diff --git a/test/fixtures/experimental/decorators-2/decoratorception-class/expected.json b/test/fixtures/experimental/decorators-2/decoratorception-class/expected.json new file mode 100644 index 0000000000..b18168fe0d --- /dev/null +++ b/test/fixtures/experimental/decorators-2/decoratorception-class/expected.json @@ -0,0 +1,269 @@ +{ + "type": "File", + "start": 0, + "end": 57, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 57, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ClassDeclaration", + "start": 0, + "end": 57, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 0, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 2 + } + }, + "expression": { + "type": "CallExpression", + "start": 1, + "end": 40, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 3, + "column": 2 + } + }, + "callee": { + "type": "Identifier", + "start": 1, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 6 + }, + "identifierName": "outer" + }, + "name": "outer" + }, + "arguments": [ + { + "type": "ObjectExpression", + "start": 7, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "properties": [ + { + "type": "ObjectProperty", + "start": 11, + "end": 37, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 28 + } + }, + "method": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 11, + "end": 16, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 7 + }, + "identifierName": "store" + }, + "name": "store" + }, + "shorthand": false, + "value": { + "type": "ClassExpression", + "start": 18, + "end": 37, + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 28 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 18, + "end": 24, + "loc": { + "start": { + "line": 2, + "column": 9 + }, + "end": { + "line": 2, + "column": 15 + } + }, + "expression": { + "type": "Identifier", + "start": 19, + "end": 24, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 2, + "column": 15 + }, + "identifierName": "inner" + }, + "name": "inner" + } + } + ], + "id": { + "type": "Identifier", + "start": 31, + "end": 34, + "loc": { + "start": { + "line": 2, + "column": 22 + }, + "end": { + "line": 2, + "column": 25 + }, + "identifierName": "Foo" + }, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 35, + "end": 37, + "loc": { + "start": { + "line": 2, + "column": 26 + }, + "end": { + "line": 2, + "column": 28 + } + }, + "body": [] + } + } + } + ] + } + ] + } + } + ], + "id": { + "type": "Identifier", + "start": 47, + "end": 50, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 9 + }, + "identifierName": "Bar" + }, + "name": "Bar" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 51, + "end": 57, + "loc": { + "start": { + "line": 4, + "column": 10 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "body": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/experimental/decorators-2/decoratorception-method/actual.js b/test/fixtures/experimental/decorators-2/decoratorception-method/actual.js new file mode 100644 index 0000000000..eb9f1b073c --- /dev/null +++ b/test/fixtures/experimental/decorators-2/decoratorception-method/actual.js @@ -0,0 +1,9 @@ +class Bar{ + @outer( + @classDec class { + @inner + innerMethod() {} + } + ) + outerMethod() {} +} diff --git a/test/fixtures/experimental/decorators-2/decoratorception-method/expected.json b/test/fixtures/experimental/decorators-2/decoratorception-method/expected.json new file mode 100644 index 0000000000..472e83b492 --- /dev/null +++ b/test/fixtures/experimental/decorators-2/decoratorception-method/expected.json @@ -0,0 +1,351 @@ +{ + "type": "File", + "start": 0, + "end": 112, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 112, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ClassDeclaration", + "start": 0, + "end": 112, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 9 + }, + "identifierName": "Bar" + }, + "name": "Bar" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 9, + "end": 112, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 9, + "column": 1 + } + }, + "body": [ + { + "type": "ClassMethod", + "start": 13, + "end": 110, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 8, + "column": 18 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 13, + "end": 91, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "expression": { + "type": "CallExpression", + "start": 14, + "end": 91, + "loc": { + "start": { + "line": 2, + "column": 3 + }, + "end": { + "line": 7, + "column": 3 + } + }, + "callee": { + "type": "Identifier", + "start": 14, + "end": 19, + "loc": { + "start": { + "line": 2, + "column": 3 + }, + "end": { + "line": 2, + "column": 8 + }, + "identifierName": "outer" + }, + "name": "outer" + }, + "arguments": [ + { + "type": "ClassExpression", + "start": 25, + "end": 87, + "loc": { + "start": { + "line": 3, + "column": 4 + }, + "end": { + "line": 6, + "column": 5 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 25, + "end": 34, + "loc": { + "start": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "expression": { + "type": "Identifier", + "start": 26, + "end": 34, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 13 + }, + "identifierName": "classDec" + }, + "name": "classDec" + } + } + ], + "id": null, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 41, + "end": 87, + "loc": { + "start": { + "line": 3, + "column": 20 + }, + "end": { + "line": 6, + "column": 5 + } + }, + "body": [ + { + "type": "ClassMethod", + "start": 50, + "end": 80, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 5, + "column": 22 + } + }, + "decorators": [ + { + "type": "Decorator", + "start": 50, + "end": 56, + "loc": { + "start": { + "line": 4, + "column": 6 + }, + "end": { + "line": 4, + "column": 12 + } + }, + "expression": { + "type": "Identifier", + "start": 51, + "end": 56, + "loc": { + "start": { + "line": 4, + "column": 7 + }, + "end": { + "line": 4, + "column": 12 + }, + "identifierName": "inner" + }, + "name": "inner" + } + } + ], + "static": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 64, + "end": 75, + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 17 + }, + "identifierName": "innerMethod" + }, + "name": "innerMethod" + }, + "kind": "method", + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 78, + "end": 80, + "loc": { + "start": { + "line": 5, + "column": 20 + }, + "end": { + "line": 5, + "column": 22 + } + }, + "body": [], + "directives": [] + } + } + ] + } + } + ] + } + } + ], + "static": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 94, + "end": 105, + "loc": { + "start": { + "line": 8, + "column": 2 + }, + "end": { + "line": 8, + "column": 13 + }, + "identifierName": "outerMethod" + }, + "name": "outerMethod" + }, + "kind": "method", + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 108, + "end": 110, + "loc": { + "start": { + "line": 8, + "column": 16 + }, + "end": { + "line": 8, + "column": 18 + } + }, + "body": [], + "directives": [] + } + } + ] + } + } + ], + "directives": [] + } +}