Added ES6 classes support.

This commit is contained in:
Ingvar Stepanyan 2014-07-24 15:48:48 +03:00 committed by Marijn Haverbeke
parent eadda16c42
commit 44a4f8c016
2 changed files with 84 additions and 42 deletions

View File

@ -281,6 +281,7 @@
var _let = {keyword: "let"}, _const = {keyword: "const"};
var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
var _this = {keyword: "this"};
var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true}, _static = {keyword: "static"};
// The keywords that denote values.
@ -305,7 +306,8 @@
"instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
"typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
"void": {keyword: "void", prefix: true, beforeExpr: true},
"delete": {keyword: "delete", prefix: true, beforeExpr: true}};
"delete": {keyword: "delete", prefix: true, beforeExpr: true},
"class": _class, "extends": _extends, "static": _static};
// Punctuation token types. Again, the `type` property is purely for debugging.
@ -424,7 +426,7 @@
var isEcma5AndLessKeyword = makePredicate(ecma5AndLessKeywords);
var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const");
var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const class extends static");
var isKeyword = isEcma5AndLessKeyword;
@ -1217,6 +1219,7 @@
case _do: return parseDoStatement(node);
case _for: return parseForStatement(node);
case _function: return parseFunctionStatement(node);
case _class: return parseClass(node, true);
case _if: return parseIfStatement(node);
case _return: return parseReturnStatement(node);
case _switch: return parseSwitchStatement(node);
@ -1731,6 +1734,9 @@
next();
return parseFunction(node, false);
case _class:
return parseClass(startNode(), false);
case _new:
return parseNew();
@ -1776,7 +1782,7 @@
if (options.allowTrailingCommas && eat(_braceR)) break;
} else first = false;
var prop = startNode(), isGetSet = false, kind;
var prop = startNode(), kind;
prop.key = parsePropertyName();
if (options.ecmaVersion >= 6) {
prop.method = false;
@ -1794,7 +1800,7 @@
prop.value = func;
} else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" &&
(prop.key.name === "get" || prop.key.name === "set")) {
isGetSet = sawGetSet = true;
sawGetSet = true;
kind = prop.kind = prop.key.name;
prop.key = parsePropertyName();
if (tokType !== _parenL) unexpected();
@ -1805,33 +1811,38 @@
prop.value = func;
} else unexpected();
// getters and setters are not allowed to clash — either with
// each other or with an init property — and in strict mode,
// init properties are also not allowed to be repeated.
if (prop.key.type === "Identifier" && (strict || sawGetSet)) {
for (var i = 0; i < node.properties.length; ++i) {
var other = node.properties[i];
if (other.key.name === prop.key.name) {
var conflict = kind == other.kind || isGetSet && other.kind === "init" ||
kind === "init" && (other.kind === "get" || other.kind === "set");
if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false;
if (conflict) raise(prop.key.start, "Redefinition of property");
}
}
}
node.properties.push(finishNode(prop, "Property"));
addProperty(node.properties, finishNode(prop, "Property"), sawGetSet, "init");
}
return finishNode(node, "ObjectExpression");
}
// Add property to list with keeping in mind and checking that
// object/class getters and setters are not allowed to clash —
// either with each other or with an init property — and in
// strict mode, init properties are also not allowed to be repeated.
function addProperty(props, current, sawGetSet, defaultKind) {
if (current.key.type === "Identifier" && (strict || sawGetSet)) {
var kind = current.kind, isGetSet = kind !== defaultKind;
for (var i = 0; i < props.length; ++i) {
var other = props[i];
if (other.key.name === current.key.name && other.static === current.static) {
var conflict = kind == other.kind || isGetSet && other.kind === defaultKind ||
kind === defaultKind && (other.kind !== defaultKind);
if (conflict && !strict && kind === defaultKind && other.kind === defaultKind) conflict = false;
if (conflict) raise(current.key.start, "Redefinition of property");
}
}
}
props.push(current);
}
function parsePropertyName() {
if (tokType === _num || tokType === _string) return parseExprAtom();
return parseIdent(true);
}
// Parse a function declaration or literal (depending on the
// `isStatement` parameter).
// Initialize empty function node with given name.
function initFunction(node, id) {
node.id = id || null;
@ -1844,6 +1855,9 @@
return node;
}
// Parse a function declaration or literal (depending on the
// `isStatement` parameter).
function parseFunction(node, isStatement, allowExpression) {
initFunction(node, tokType === _name ? parseIdent() : isStatement ? unexpected() : null);
parseFunctionParams(node);
@ -1946,6 +1960,36 @@
}
}
// Parse a class declaration or literal (depending on the
// `isStatement` parameter).
function parseClass(node, isStatement) {
next();
node.id = tokType === _name ? parseIdent() : isStatement ? unexpected() : null;
node.superClass = eat(_extends) ? parseExpression() : null;
var classBody = startNode(), sawGetSet = false;
classBody.body = [];
expect(_braceL);
while (!eat(_braceR)) {
var method = startNode();
method.static = !!eat(_static);
method.key = parseIdent(true);
if (method.key.type === "Identifier" && (method.key.name === "get" || method.key.name === "set") && tokType === _name) {
method.kind = method.key.name;
method.key = parseIdent(true);
sawGetSet = true;
} else {
method.kind = "";
}
method.value = parseFunction(startNode());
setLoc(method.value, method.value.body);
addProperty(classBody.body, finishNode(method, "MethodDefinition"), sawGetSet, "");
eat(_semi);
}
node.body = finishNode(classBody, "ClassBody");
return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
}
// Parses a comma-separated list of expressions, and returns them as
// an array. `close` is the token type that ends the list, and
// `allowEmpty` can be turned on to allow subsequent commas with

View File

@ -9205,10 +9205,10 @@ test("\"use strict\"; (class A {constructor() { super() }})", {
end: {line: 1, column: 47}
}
},
range: [40, 48],
range: [40, 47],
loc: {
start: {line: 1, column: 40},
end: {line: 1, column: 48}
end: {line: 1, column: 47}
}
}],
range: [38, 49],
@ -9240,10 +9240,10 @@ test("\"use strict\"; (class A {constructor() { super() }})", {
end: {line: 1, column: 50}
}
},
range: [15, 50],
range: [14, 51],
loc: {
start: {line: 1, column: 15},
end: {line: 1, column: 50}
start: {line: 1, column: 14},
end: {line: 1, column: 51}
}
},
range: [14, 51],
@ -9257,8 +9257,7 @@ test("\"use strict\"; (class A {constructor() { super() }})", {
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 51}
},
comments: []
}
}, {
ecmaVersion: 6,
ranges: true,
@ -9546,10 +9545,10 @@ test("\"use strict\"; (class A { static constructor() { super() }})", {
end: {line: 1, column: 55}
}
},
range: [48, 56],
range: [48, 55],
loc: {
start: {line: 1, column: 48},
end: {line: 1, column: 56}
end: {line: 1, column: 55}
}
}],
range: [46, 57],
@ -9581,10 +9580,10 @@ test("\"use strict\"; (class A { static constructor() { super() }})", {
end: {line: 1, column: 58}
}
},
range: [15, 58],
range: [14, 59],
loc: {
start: {line: 1, column: 15},
end: {line: 1, column: 58}
start: {line: 1, column: 14},
end: {line: 1, column: 59}
}
},
range: [14, 59],
@ -9598,8 +9597,7 @@ test("\"use strict\"; (class A { static constructor() { super() }})", {
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 59}
},
comments: []
}
}, {
ecmaVersion: 6,
ranges: true,
@ -10472,17 +10470,17 @@ test("class A { set foo(v) {} get foo() {} }", {
locations: true
});
testFail("class A { get foo() {} get foo() {} }", "Unexpected token (1:31)", {ecmaVersion: 6});
testFail("class A { get foo() {} get foo() {} }", "Redefinition of property (1:27)", {ecmaVersion: 6});
testFail("class A { set foo(v) {} set foo(v) {} }", "Unexpected token (1:32)", {ecmaVersion: 6});
testFail("class A { set foo(v) {} set foo(v) {} }", "Redefinition of property (1:28)", {ecmaVersion: 6});
testFail("class A { get foo() {} foo() {} }", "Unexpected token (1:27)", {ecmaVersion: 6});
testFail("class A { get foo() {} foo() {} }", "Redefinition of property (1:23)", {ecmaVersion: 6});
testFail("class A { foo() {} get foo() {} }", "Unexpected token (1:27)", {ecmaVersion: 6});
testFail("class A { foo() {} get foo() {} }", "Redefinition of property (1:23)", {ecmaVersion: 6});
testFail("class A { set foo(v) {} foo() {} }", "Unexpected token (1:28)", {ecmaVersion: 6});
testFail("class A { set foo(v) {} foo() {} }", "Redefinition of property (1:24)", {ecmaVersion: 6});
testFail("class A { foo() {} set foo(v) {} }", "Unexpected token (1:27)", {ecmaVersion: 6});
testFail("class A { foo() {} set foo(v) {} }", "Redefinition of property (1:23)", {ecmaVersion: 6});
// ES6: Computed Properties