diff --git a/acorn.js b/acorn.js index c23eb47aef..00127e463d 100644 --- a/acorn.js +++ b/acorn.js @@ -2040,7 +2040,7 @@ var first = true; if (!node.body) node.body = []; while (tokType !== _eof) { - var stmt = parseStatement(true); + var stmt = parseStatement(true, true); node.body.push(stmt); if (first && isUseStrict(stmt)) setStrict(true); first = false; @@ -2059,7 +2059,7 @@ // `if (foo) /blah/.exec(foo);`, where looking at the previous token // does not help. - function parseStatement(topLevel) { + function parseStatement(declaration, topLevel) { var starttype = tokType, node = startNode(); // Most types of statements are recognized by the keyword they @@ -2071,14 +2071,19 @@ case _debugger: return parseDebuggerStatement(node); case _do: return parseDoStatement(node); case _for: return parseForStatement(node); - case _function: return parseFunctionStatement(node); - case _class: return parseClass(node, true); + case _function: + if (!declaration && options.ecmaVersion >= 6) unexpected(); + return parseFunctionStatement(node); + case _class: + if (!declaration) unexpected(); + return parseClass(node, true); case _if: return parseIfStatement(node); case _return: return parseReturnStatement(node); case _switch: return parseSwitchStatement(node); case _throw: return parseThrowStatement(node); case _try: return parseTryStatement(node); - case _var: case _let: case _const: return parseVarStatement(node, starttype.keyword); + case _let: case _const: if (!declaration) unexpected(); // NOTE: falls through to _var + case _var: return parseVarStatement(node, starttype.keyword); case _while: return parseWhileStatement(node); case _with: return parseWithStatement(node); case _braceL: return parseBlock(); // no point creating a function for this @@ -2134,7 +2139,7 @@ function parseDoStatement(node) { next(); labels.push(loopLabel); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); expect(_while); node.test = parseParenExpression(); @@ -2184,8 +2189,8 @@ function parseIfStatement(node) { next(); node.test = parseParenExpression(); - node.consequent = parseStatement(); - node.alternate = eat(_else) ? parseStatement() : null; + node.consequent = parseStatement(false); + node.alternate = eat(_else) ? parseStatement(false) : null; return finishNode(node, "IfStatement"); } @@ -2229,7 +2234,7 @@ expect(_colon); } else { if (!cur) unexpected(); - cur.consequent.push(parseStatement()); + cur.consequent.push(parseStatement(true)); } } if (cur) finishNode(cur, "SwitchCase"); @@ -2280,7 +2285,7 @@ next(); node.test = parseParenExpression(); labels.push(loopLabel); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); return finishNode(node, "WhileStatement"); } @@ -2289,7 +2294,7 @@ if (strict) raise(tokStart, "'with' in strict mode"); next(); node.object = parseParenExpression(); - node.body = parseStatement(); + node.body = parseStatement(false); return finishNode(node, "WithStatement"); } @@ -2303,7 +2308,7 @@ if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared"); var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null; labels.push({name: maybeName, kind: kind}); - node.body = parseStatement(); + node.body = parseStatement(true); labels.pop(); node.label = expr; return finishNode(node, "LabeledStatement"); @@ -2334,7 +2339,7 @@ node.body = []; expect(_braceL); while (!eat(_braceR)) { - var stmt = parseStatement(); + var stmt = parseStatement(true); node.body.push(stmt); if (first && allowStrict && isUseStrict(stmt)) { oldStrict = strict; @@ -2357,7 +2362,7 @@ expect(_semi); node.update = tokType === _parenR ? null : parseExpression(); expect(_parenR); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); return finishNode(node, "ForStatement"); } @@ -2371,7 +2376,7 @@ node.left = init; node.right = parseExpression(); expect(_parenR); - node.body = parseStatement(); + node.body = parseStatement(false); labels.pop(); return finishNode(node, type); } @@ -2929,6 +2934,7 @@ classBody.body = []; expect(_braceL); while (!eat(_braceR)) { + if (eat(_semi)) continue; var method = startNode(); var isGenerator = eat(_star); parsePropertyName(method); @@ -2951,7 +2957,6 @@ } method.value = parseMethod(isGenerator); classBody.body.push(finishNode(method, "MethodDefinition")); - eat(_semi); } node.body = finishNode(classBody, "ClassBody"); return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); @@ -3007,7 +3012,7 @@ next(); // export var|const|let|function|class ...; if (tokType === _var || tokType === _const || tokType === _let || tokType === _function || tokType === _class) { - node.declaration = parseStatement(); + node.declaration = parseStatement(true); node['default'] = false; node.specifiers = null; node.source = null; diff --git a/acorn_loose.js b/acorn_loose.js index 03d92b7352..a6542ee7d0 100644 --- a/acorn_loose.js +++ b/acorn_loose.js @@ -854,6 +854,7 @@ eat(tt.braceL); if (curIndent + 1 < indent) { indent = curIndent; line = curLineStart; } while (!closes(tt.braceR, indent, line)) { + if (isClass && semicolon()) continue; var prop = startNode(), isGenerator; if (options.ecmaVersion >= 6) { if (isClass) { @@ -904,7 +905,6 @@ if (isClass) { node.body.body.push(finishNode(prop, "MethodDefinition")); - semicolon(); } else { node.properties.push(finishNode(prop, "Property")); eat(tt.comma); diff --git a/test/tests-harmony.js b/test/tests-harmony.js index eb8b8c5812..0ea8758cf7 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -8859,6 +8859,42 @@ test("class A { foo() {} get foo() {} }",{ locations: true }); +test("class Semicolon { ; }", { + type: "Program", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 21} + }, + body: [{ + type: "ClassDeclaration", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 21} + }, + id: { + type: "Identifier", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 15} + }, + name: "Semicolon" + }, + superClass: null, + body: { + type: "ClassBody", + loc: { + start: {line: 1, column: 16}, + end: {line: 1, column: 21} + }, + body: [] + } + }] +}, { + ecmaVersion: 6, + ranges: true, + locations: true +}); + // ES6: Computed Properties test("({[x]: 10})", { @@ -14840,3 +14876,8 @@ test("`${/\d/.exec('1')[0]}`", { }, { ecmaVersion: 6 }); + +testFail("if (1) let x = 10;", "Unexpected token (1:7)", {ecmaVersion: 6}); +testFail("for (;;) const x = 10;", "Unexpected token (1:9)", {ecmaVersion: 6}); +testFail("while (1) function foo(){}", "Unexpected token (1:10)", {ecmaVersion: 6}); +testFail("if (1) ; else class Cls {}", "Unexpected token (1:14)", {ecmaVersion: 6}); diff --git a/util/walk.js b/util/walk.js index 117f56f93c..da7ca9f7e1 100644 --- a/util/walk.js +++ b/util/walk.js @@ -285,7 +285,15 @@ c(node.object, st, "Expression"); if (node.computed) c(node.property, st, "Expression"); }; - base.Identifier = base.Literal = base.ExportDeclaration = base.ImportDeclaration = ignore; + base.ExportDeclaration = function (node, st, c) { + c(node.declaration, st); + }; + base.ImportDeclaration = function (node, st, c) { + node.specifiers.forEach(function (specifier) { + c(specifier, st); + }); + }; + base.ImportSpecifier = base.ImportBatchSpecifier = base.Identifier = base.Literal = ignore; base.TaggedTemplateExpression = function(node, st, c) { c(node.tag, st, "Expression");