add support for class decorators

This commit is contained in:
Sebastian McKenzie 2015-03-22 04:07:38 +11:00
parent 6128fd9687
commit 37072737b9
5 changed files with 41 additions and 48 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -2056,3 +2056,5 @@ test('export async function foo(){}', {
sourceType: "module",
features: { "es7.asyncFunctions": true }
});
// ES7 decorators