diff --git a/src/state.js b/src/state.js index c8fb9928ba..ecc2e77cb0 100755 --- a/src/state.js +++ b/src/state.js @@ -51,6 +51,8 @@ export function Parser(options, input, startPos) { // Labels in scope. this.labels = [] + this.decorators = [] + // If enabled, skip leading hashbang line. if (this.pos === 0 && this.options.allowHashBang && this.input.slice(0, 2) === '#!') this.skipLineComment(2) diff --git a/src/statement.js b/src/statement.js index cd179ad643..0e77cdc308 100755 --- a/src/statement.js +++ b/src/statement.js @@ -51,9 +51,23 @@ pp.parseStatement = function(declaration, topLevel) { case tt._function: if (!declaration && this.options.ecmaVersion >= 6) this.unexpected() return this.parseFunctionStatement(node) + + case tt.at: + while (this.type === tt.at) { + this.decorators.push(this.parseDecorator()); + } + if (this.type !== tt._class) { + this.raise(this.start, "Leading decorators must be attached to a class declaration"); + } + case tt._class: if (!declaration) this.unexpected() + if (this.decorators.length) { + node.decorators = this.decorators + this.decorators = [] + } return this.parseClass(node, true) + case tt._if: return this.parseIfStatement(node) case tt._return: return this.parseReturnStatement(node) case tt._switch: return this.parseSwitchStatement(node) @@ -99,6 +113,13 @@ pp.parseStatement = function(declaration, topLevel) { } } +pp.parseDecorator = function() { + let node = this.startNode() + this.next() + node.expression = this.parseMaybeAssign() + return this.finishNode(node, "Decorator") +} + pp.parseBreakContinueStatement = function(node, keyword) { let isBreak = keyword == "break" this.next() @@ -423,52 +444,6 @@ pp.parseFunctionParams = function(node) { // Parse a class declaration or literal (depending on the // `isStatement` parameter). -pp.parseClass = function(node, isStatement) { - this.next() - node.id = this.type === tt.name ? this.parseIdent() : isStatement ? this.unexpected() : null - node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null - let classBody = this.startNode() - classBody.body = [] - this.expect(tt.braceL) - while (!this.eat(tt.braceR)) { - if (this.eat(tt.semi)) continue - let method = this.startNode() - let isGenerator = this.eat(tt.star) - this.parsePropertyName(method) - if (this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" && - method.key.name === "static") { - if (isGenerator) this.unexpected() - method['static'] = true - isGenerator = this.eat(tt.star) - this.parsePropertyName(method) - } else { - method['static'] = false - } - if (this.options.features["es7.asyncFunctions"] && this.type !== tt.parenL && - !method.computed && method.key.type === "Identifier" && method.key.name === "async") { - isAsync = true; - this.parsePropertyName(method); - } - method.kind = "method" - if (!method.computed && !isGenerator) { - if (method.key.type === "Identifier") { - if (this.type !== tt.parenL && (method.key.name === "get" || method.key.name === "set")) { - method.kind = method.key.name - this.parsePropertyName(method) - } else if (!method['static'] && method.key.name === "constructor") { - method.kind = "constructor" - } - } else if (!method['static'] && method.key.type === "Literal" && method.key.value === "constructor") { - method.kind = "constructor" - } - } - method.value = this.parseMethod(isGenerator) - classBody.body.push(this.finishNode(method, "MethodDefinition")) - } - node.body = this.finishNode(classBody, "ClassBody") - return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression") -} - pp.parseClass = function(node, isStatement) { this.next() this.parseClassId(node, isStatement) @@ -476,8 +451,13 @@ pp.parseClass = function(node, isStatement) { var classBody = this.startNode() classBody.body = [] this.expect(tt.braceL) + var decorators = [] while (!this.eat(tt.braceR)) { if (this.eat(tt.semi)) continue + if (this.options.features["es7.decorators"] && this.type === tt.at) { + decorators.push(this.parseDecorator()); + continue; + } var method = this.startNode() var isGenerator = this.eat(tt.star), isAsync = false this.parsePropertyName(method) @@ -508,8 +488,15 @@ pp.parseClass = function(node, isStatement) { method.kind = "constructor" } } + if (this.options.features["es7.decorators"] && decorators.length) { + method.decorators = decorators + decorators = [] + } this.parseClassMethod(classBody, method, isGenerator, isAsync) } + if (decorators.length) { + raise(this.start, "You have trailing decorators with no method"); + } node.body = this.finishNode(classBody, "ClassBody") return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression") } @@ -540,11 +527,11 @@ pp.parseExport = function(node) { } if (this.eat(tt._default)) { // export default ... let expr = this.parseMaybeAssign() - let needsSemi = false + let needsSemi = true if (expr.id) switch (expr.type) { case "FunctionExpression": expr.type = "FunctionDeclaration"; break case "ClassExpression": expr.type = "ClassDeclaration"; break - default: needsSemi = true + default: needsSemi = false } node.declaration = expr if (needsSemi) this.semicolon() diff --git a/src/tokenize.js b/src/tokenize.js index 7fffbc5a07..fbb0617659 100755 --- a/src/tokenize.js +++ b/src/tokenize.js @@ -322,6 +322,7 @@ pp.getTokenFromCode = function(code) { case 125: ++this.pos; return this.finishToken(tt.braceR) case 58: ++this.pos; return this.finishToken(tt.colon) case 63: ++this.pos; return this.finishToken(tt.question) + case 64: ++this.pos; return this.finishToken(tt.at) case 96: // '`' if (this.options.ecmaVersion < 6) break diff --git a/src/tokentype.js b/src/tokentype.js index 99e6f44a58..aa0ecdabe2 100755 --- a/src/tokentype.js +++ b/src/tokentype.js @@ -61,6 +61,7 @@ export const types = { ellipsis: new TokenType("...", beforeExpr), backQuote: new TokenType("`", startsExpr), dollarBraceL: new TokenType("${", {beforeExpr: true, startsExpr: true}), + at: new TokenType("@"), // Operators. These carry several kinds of properties to help the // parser use them properly (the presence of these properties is diff --git a/test/tests-babel.js b/test/tests-babel.js index 538a2ce9e2..c0d2a26e3e 100644 --- a/test/tests-babel.js +++ b/test/tests-babel.js @@ -2056,3 +2056,5 @@ test('export async function foo(){}', { sourceType: "module", features: { "es7.asyncFunctions": true } }); + +// ES7 decorators