From 44a4f8c01608b11daa8964dd7240034b78f8e90a Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 24 Jul 2014 15:48:48 +0300 Subject: [PATCH] Added ES6 classes support. --- acorn.js | 88 ++++++++++++++++++++++++++++++++----------- test/tests-harmony.js | 38 +++++++++---------- 2 files changed, 84 insertions(+), 42 deletions(-) diff --git a/acorn.js b/acorn.js index da572a8ce3..aa7b519c92 100644 --- a/acorn.js +++ b/acorn.js @@ -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 diff --git a/test/tests-harmony.js b/test/tests-harmony.js index cc9e13d612..e18edadee6 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -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