diff --git a/acorn.js b/acorn.js index f9321ecfe7..2c7f61d8c3 100644 --- a/acorn.js +++ b/acorn.js @@ -167,7 +167,13 @@ if (options.strictMode) { strict = true; } - isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword; + if (options.ecmaVersion >= 7) { + isKeyword = isEcma7Keyword; + } else if (options.ecmaVersion === 6) { + isKeyword = isEcma6Keyword; + } else { + isKeyword = isEcma5AndLessKeyword; + } } // The `getLineInfo` function is mostly useful when the @@ -292,7 +298,7 @@ // that `break` and `continue` have somewhere to jump to, and // `strict` indicates whether strict mode is on. - var inFunction, inGenerator, labels, strict, + var inFunction, inGenerator, inAsync, labels, strict, inXJSChild, inXJSTag, inXJSChildExpression; // This counter is used for checking that arrow expressions did @@ -309,7 +315,7 @@ function initParserState() { lastStart = lastEnd = tokPos; if (options.locations) lastEndLoc = new Position; - inFunction = inGenerator = strict = false; + inFunction = inGenerator = inAsync = strict = false; labels = []; readToken(); } @@ -376,6 +382,7 @@ var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true}; var _export = {keyword: "export"}, _import = {keyword: "import"}; var _yield = {keyword: "yield", beforeExpr: true}; + var _async = {keyword: "async"}, _await = {keyword: "await", beforeExpr: true}; // The keywords that denote values. @@ -402,7 +409,8 @@ "void": {keyword: "void", prefix: true, beforeExpr: true}, "delete": {keyword: "delete", prefix: true, beforeExpr: true}, "class": _class, "extends": _extends, - "export": _export, "import": _import, "yield": _yield}; + "export": _export, "import": _import, "yield": _yield, + "await": _await, "async": _async}; // Punctuation token types. Again, the `type` property is purely for debugging. @@ -530,7 +538,11 @@ var isEcma5AndLessKeyword = makePredicate(ecma5AndLessKeywords); - var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const class extends export import yield"); + var ecma6AndLessKeywords = ecma5AndLessKeywords + " let const class extends export import yield"; + + var isEcma6Keyword = makePredicate(ecma6AndLessKeywords); + + var isEcma7Keyword = makePredicate(ecma6AndLessKeywords + " async await"); var isKeyword = isEcma5AndLessKeyword; @@ -1896,6 +1908,7 @@ case _debugger: return parseDebuggerStatement(node); case _do: return parseDoStatement(node); case _for: return parseForStatement(node); + case _async: return parseAsync(node, true); case _function: return parseFunctionStatement(node); case _class: return parseClass(node, true); case _if: return parseIfStatement(node); @@ -1997,7 +2010,46 @@ function parseFunctionStatement(node) { next(); - return parseFunction(node, true); + return parseFunction(node, true, false); + } + + function parseAsync(node, isStatement) { + if (options.ecmaVersion < 7) { + unexpected(); + } + + next(); + + switch (tokType) { + case _function: + next(); + return parseFunction(node, isStatement, true); + + if (!isStatement) unexpected(); + + case _name: + var id = parseIdent(tokType !== _name); + if (eat(_arrow)) { + return parseArrowExpression(node, [id], true); + } + + case _parenL: + var oldParenL = ++metParenL; + var exprList = []; + next(); + if (tokType !== _parenR) { + var val = parseExpression(); + exprList = val.type === "SequenceExpression" ? val.expressions : [val]; + } + expect(_parenR); + // if '=>' follows '(...)', convert contents to arguments + if (metParenL === oldParenL && eat(_arrow)) { + return parseArrowExpression(node, exprList, true); + } + + default: + unexpected(); + } } function parseIfStatement(node) { @@ -2380,6 +2432,9 @@ case _yield: if (inGenerator) return parseYield(); + case _await: + if (inAsync) return parseAwait(); + case _name: var start = storeCurrentPos(); var id = parseIdent(tokType !== _name); @@ -2461,10 +2516,13 @@ case _braceL: return parseObj(); + case _async: + return parseAsync(startNode(), false); + case _function: var node = startNode(); next(); - return parseFunction(node, false); + return parseFunction(node, false, false); case _class: return parseClass(startNode(), false); @@ -2552,7 +2610,11 @@ if (options.allowTrailingCommas && eat(_braceR)) break; } else first = false; - var prop = startNode(), isGenerator; + var prop = startNode(), isGenerator, isAsync; + if (options.ecmaVersion >= 7) { + isAsync = eat(_async); + if (isAsync && tokType === _star) unexpected(); + } if (options.ecmaVersion >= 6) { prop.method = false; prop.shorthand = false; @@ -2565,13 +2627,13 @@ } else if (options.ecmaVersion >= 6 && tokType === _parenL) { prop.kind = "init"; prop.method = true; - prop.value = parseMethod(isGenerator); + prop.value = parseMethod(isGenerator, isAsync); } else if (options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set")) { - if (isGenerator) unexpected(); + if (isGenerator || isAsync) unexpected(); prop.kind = prop.key.name; parsePropertyName(prop); - prop.value = parseMethod(false); + prop.value = parseMethod(false, false); } else if (options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") { prop.kind = "init"; prop.value = prop.key; @@ -2600,22 +2662,26 @@ // Initialize empty function node. - function initFunction(node) { + function initFunction(node, isAsync) { node.id = null; node.params = []; if (options.ecmaVersion >= 6) { node.defaults = []; node.rest = null; node.generator = false; - } + } + if (options.ecmaVersion >= 7) { + node.async = isAsync; + } } // Parse a function declaration or literal (depending on the // `isStatement` parameter). - function parseFunction(node, isStatement, allowExpressionBody) { - initFunction(node); + function parseFunction(node, isStatement, isAsync, allowExpressionBody) { + initFunction(node, isAsync); if (options.ecmaVersion >= 6) { + if (isAsync && tokType === _star) unexpected(); node.generator = eat(_star); } if (isStatement || tokType === _name) { @@ -2628,9 +2694,9 @@ // Parse object or class method. - function parseMethod(isGenerator) { + function parseMethod(isGenerator, isAsync) { var node = startNode(); - initFunction(node); + initFunction(node, isAsync); parseFunctionParams(node); var allowExpressionBody; if (options.ecmaVersion >= 6) { @@ -2645,8 +2711,8 @@ // Parse arrow function expression with given parameters. - function parseArrowExpression(node, params) { - initFunction(node); + function parseArrowExpression(node, params, isAsync) { + initFunction(node, isAsync); var defaults = node.defaults, hasDefaults = false; @@ -2714,7 +2780,9 @@ function parseFunctionBody(node, allowExpression) { var isExpression = allowExpression && tokType !== _braceL; - + + var oldInAsync = inAsync; + inAsync = node.async; if (isExpression) { node.body = parseExpression(true); node.expression = true; @@ -2727,6 +2795,7 @@ node.expression = false; inFunction = oldInFunc; inGenerator = oldInGen; labels = oldLabels; } + inAsync = oldInAsync; // If this is a strict mode function, verify that argument names // are not repeated, and it does not try to bind the words `eval` @@ -2760,17 +2829,22 @@ } else { method['static'] = false; } + var isAsync = false; + if (options.ecmaVersion >= 7) { + isAsync = eat(_async); + if (isAsync && tokType === _star) unexpected(); + } var isGenerator = eat(_star); parsePropertyName(method); if (tokType !== _parenL && !method.computed && method.key.type === "Identifier" && (method.key.name === "get" || method.key.name === "set")) { - if (isGenerator) unexpected(); + if (isGenerator || isAsync) unexpected(); method.kind = method.key.name; parsePropertyName(method); } else { method.kind = ""; } - method.value = parseMethod(isGenerator); + method.value = parseMethod(isGenerator, isAsync); classBody.body.push(finishNode(method, "MethodDefinition")); eat(_semi); } @@ -2969,12 +3043,25 @@ node.delegate = false; node.argument = null; } else { - node.delegate = eat(_star); - node.argument = parseExpression(true); + node.delegate = eat(_star); + node.argument = parseExpression(true); } return finishNode(node, "YieldExpression"); } + // Parses await expression inside async function. + + function parseAwait() { + var node = startNode(); + next(); + if (eat(_semi) || canInsertSemicolon()) { + unexpected(); + } + node.delegate = eat(_star); + node.argument = parseExpression(true); + return finishNode(node, "AwaitExpression"); + } + // Parses array and generator comprehensions. function parseComprehension(node, isGenerator) { diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 2302f58f60..0600d736c0 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -13558,6 +13558,771 @@ test("/[a-z]/u", { ecmaVersion: 6 }); +// ES7: Async Functions + +test('async function foo(promise) { await promise; }', { + type: "Program", + body: [{ + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "foo", + loc: { + start: {line: 1, column: 15}, + end: {line: 1, column: 18} + } + }, + params: [{ + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 19}, + end: {line: 1, column: 26} + } + }], + defaults: [], + body: { + type: "BlockStatement", + body: [{ + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 36}, + end: {line: 1, column: 43} + } + }, + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 43} + } + }, + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 44} + } + }], + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 46} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 46} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('(function(x) { async function inner() { await x } })', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 11} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "FunctionDeclaration", + id: { + type: "Identifier", + name: "inner", + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 35} + } + }, + params: [], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 46}, + end: {line: 1, column: 47} + } + }, + loc: { + start: {line: 1, column: 40}, + end: {line: 1, column: 47} + } + }, + loc: { + start: {line: 1, column: 40}, + end: {line: 1, column: 47} + } + } + ], + loc: { + start: {line: 1, column: 38}, + end: {line: 1, column: 49} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 15}, + end: {line: 1, column: 49} + } + } + ], + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 51} + } + }, + rest: null, + generator: false, + expression: false, + loc: { + start: {line: 1, column: 1}, + end: {line: 1, column: 51} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 52} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('var foo = async function(promise) { await promise; }', { + type: "Program", + body: [{ + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "foo", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 7} + } + }, + init: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 25}, + end: {line: 1, column: 32} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 42}, + end: {line: 1, column: 49} + } + }, + loc: { + start: {line: 1, column: 36}, + end: {line: 1, column: 49} + } + }, + loc: { + start: {line: 1, column: 36}, + end: {line: 1, column: 50} + } + } + ], + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 52} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 52} + } + }, + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 52} + } + } + ], + kind: "var", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 52} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('var o = { a: 1, async foo(promise) { await promise } }', { + type: "Program", + body: [{ + type: "VariableDeclaration", + declarations: [ + { + type: "VariableDeclarator", + id: { + type: "Identifier", + name: "o", + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 5} + } + }, + init: { + type: "ObjectExpression", + properties: [ + { + type: "Property", + key: { + type: "Identifier", + name: "a", + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 11} + } + }, + value: { + type: "Literal", + value: 1, + loc: { + start: {line: 1, column: 13}, + end: {line: 1, column: 14} + } + }, + kind: "init", + method: false, + shorthand: false, + computed: false, + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 14} + } + }, + { + type: "Property", + key: { + type: "Identifier", + name: "foo", + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 25} + } + }, + value: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 26}, + end: {line: 1, column: 33} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 43}, + end: {line: 1, column: 50} + } + }, + loc: { + start: {line: 1, column: 37}, + end: {line: 1, column: 50} + } + }, + loc: { + start: {line: 1, column: 37}, + end: {line: 1, column: 50} + } + } + ], + loc: { + start: {line: 1, column: 35}, + end: {line: 1, column: 52} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 25}, + end: {line: 1, column: 52} + } + }, + kind: "init", + method: true, + shorthand: false, + computed: false, + loc: { + start: {line: 1, column: 16}, + end: {line: 1, column: 52} + } + } + ], + loc: { + start: {line: 1, column: 8}, + end: {line: 1, column: 54} + } + }, + loc: { + start: {line: 1, column: 4}, + end: {line: 1, column: 54} + } + } + ], + kind: "var", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 54} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('class Foo { async bar(promise) { await promise } }', { + type: "Program", + body: [{ + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "Foo", + loc: { + start: {line: 1, column: 6}, + end: {line: 1, column: 9} + } + }, + superClass: null, + body: { + type: "ClassBody", + body: [ + { + type: "MethodDefinition", + key: { + type: "Identifier", + name: "bar", + loc: { + start: {line: 1, column: 18}, + end: {line: 1, column: 21} + } + }, + value: { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 29} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 39}, + end: {line: 1, column: 46} + } + }, + loc: { + start: {line: 1, column: 33}, + end: {line: 1, column: 46} + } + }, + loc: { + start: {line: 1, column: 33}, + end: {line: 1, column: 46} + } + } + ], + loc: { + start: {line: 1, column: 31}, + end: {line: 1, column: 48} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 21}, + end: {line: 1, column: 48} + } + }, + kind: "", + static: false, + loc: { + start: {line: 1, column: 12}, + end: {line: 1, column: 48} + } + } + ], + loc: { + start: {line: 1, column: 10}, + end: {line: 1, column: 50} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 50} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('f(a, async promise => await promise)', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "f", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 1} + } + }, + arguments: [ + { + type: "Identifier", + name: "a", + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 3} + } + }, + { + type: "ArrowFunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 18} + } + } + ], + defaults: [], + body: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 35} + } + }, + loc: { + start: {line: 1, column: 22}, + end: {line: 1, column: 35} + } + }, + rest: null, + generator: false, + expression: true, + async: true, + loc: { + start: {line: 1, column: 5}, + end: {line: 1, column: 35} + } + } + ], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('f(a, async(x, y) => await [x, y], b)', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "f", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 1} + } + }, + arguments: [ + { + type: "Identifier", + name: "a", + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 3} + } + }, + { + type: "ArrowFunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 11}, + end: {line: 1, column: 12} + } + }, + { + type: "Identifier", + name: "y", + loc: { + start: {line: 1, column: 14}, + end: {line: 1, column: 15} + } + } + ], + defaults: [], + body: { + type: "AwaitExpression", + argument: { + type: "ArrayExpression", + elements: [ + { + type: "Identifier", + name: "x", + loc: { + start: {line: 1, column: 27}, + end: {line: 1, column: 28} + } + }, + { + type: "Identifier", + name: "y", + loc: { + start: {line: 1, column: 30}, + end: {line: 1, column: 31} + } + } + ], + loc: { + start: {line: 1, column: 26}, + end: {line: 1, column: 32} + } + }, + loc: { + start: {line: 1, column: 20}, + end: {line: 1, column: 32} + } + }, + rest: null, + generator: false, + expression: true, + async: true, + loc: { + start: {line: 1, column: 5}, + end: {line: 1, column: 32} + } + }, + { + type: "Identifier", + name: "b", + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 35} + } + } + ], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 36} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + +test('f(async function(promise) { await promise })', { + type: "Program", + body: [{ + type: "ExpressionStatement", + expression: { + type: "CallExpression", + callee: { + type: "Identifier", + name: "f", + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 1} + } + }, + arguments: [ + { + type: "FunctionExpression", + id: null, + params: [ + { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 17}, + end: {line: 1, column: 24} + } + } + ], + defaults: [], + body: { + type: "BlockStatement", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "AwaitExpression", + argument: { + type: "Identifier", + name: "promise", + loc: { + start: {line: 1, column: 34}, + end: {line: 1, column: 41} + } + }, + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 41} + } + }, + loc: { + start: {line: 1, column: 28}, + end: {line: 1, column: 41} + } + } + ], + loc: { + start: {line: 1, column: 26}, + end: {line: 1, column: 43} + } + }, + rest: null, + generator: false, + expression: false, + async: true, + loc: { + start: {line: 1, column: 2}, + end: {line: 1, column: 43} + } + } + ], + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 44} + } + }, + loc: { + start: {line: 1, column: 0}, + end: {line: 1, column: 44} + } + }] +}, { + ecmaVersion: 7, + locations: true +}); + // Harmony Invalid syntax testFail("0o", "Expected number in radix 8 (1:2)", {ecmaVersion: 6}); @@ -13670,6 +14435,10 @@ testFail("(10) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); testFail("(10, 20) => 00", "Unexpected token (1:1)", {ecmaVersion: 6}); +testFail("function foo(promise) { await promise; }", "Unexpected token (1:30)", {ecmaVersion: 7}); + +testFail("async function* foo(promise) { await promise; }", "Unexpected token (1:14)", {ecmaVersion: 7}); + testFail("yield v", "Unexpected token (1:6)", {ecmaVersion: 6}); testFail("yield 10", "Unexpected token (1:6)", {ecmaVersion: 6});