diff --git a/src/expression.js b/src/expression.js index b2fa912e4a..fc1eea4f8f 100755 --- a/src/expression.js +++ b/src/expression.js @@ -233,11 +233,19 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) { node.computed = true; this.expect(tt.bracketR); base = this.finishNode(node, "MemberExpression"); - } else if (!noCalls && this.eat(tt.parenL)) { + } else if (!noCalls && this.type === tt.parenL) { + let possibleAsync = false; + if (base.type === "Identifier" && base.name === "async" && !this.canInsertSemicolon()) possibleAsync = true; + this.next(); + let node = this.startNodeAt(startPos, startLoc); node.callee = base; node.arguments = this.parseExprList(tt.parenR, this.options.features["es7.trailingFunctionCommas"]); base = this.finishNode(node, "CallExpression"); + + if (possibleAsync && (this.type === tt.colon || this.type === tt.arrow)) { + return this.parseAsyncArrowFromCallExpression(this.startNodeAt(startPos, startLoc), node); + } } else if (this.type === tt.backQuote) { let node = this.startNodeAt(startPos, startLoc); node.tag = base; @@ -249,6 +257,12 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) { } }; +pp.parseAsyncArrowFromCallExpression = function (node, call) { + if (!this.options.features["es7.asyncFunctions"]) this.unexpected(); + this.expect(tt.arrow); + return this.parseArrowExpression(node, call.arguments, true); +}; + // Parse a no-call expression (like argument of `new` or `::` operators). pp.parseNoCallExpr = function () { @@ -291,49 +305,26 @@ pp.parseExprAtom = function (refShorthandDefaultPos) { } case tt.name: - let startPos = this.start, startLoc = this.startLoc; node = this.startNode(); let id = this.parseIdent(true); // if (this.options.features["es7.asyncFunctions"]) { - // async functions! - if (id.name === "async" && !this.canInsertSemicolon()) { - // arrow functions - if (this.type === tt.parenL) { - let expr = this.parseParenAndDistinguishExpression(startPos, startLoc, true, true); - if (expr && expr.type === "ArrowFunctionExpression") { - return expr; - } else { - node.callee = id; - if (!expr) { - node.arguments = []; - } else if (expr.type === "SequenceExpression") { - node.arguments = expr.expressions; - } else { - node.arguments = [expr]; - } - return this.parseSubscripts(this.finishNode(node, "CallExpression"), startPos, startLoc); - } - } else if (this.type === tt.name) { - id = this.parseIdent(); - this.expect(tt.arrow); - return this.parseArrowExpression(node, [id], true); - } - - // normal functions - if (this.type === tt._function && !this.canInsertSemicolon()) { - this.next(); - return this.parseFunction(node, false, false, true); - } - } else if (id.name === "await") { + if (id.name === "await") { if (this.inAsync) return this.parseAwait(node); + } else if (id.name === "async" && this.type === tt._function && !this.canInsertSemicolon()) { + this.next(); + return this.parseFunction(node, false, false, true); + } else if (id.name === "async" && this.type === tt.name) { + var params = [this.parseIdent()]; + this.expect(tt.arrow); + // var foo = bar => {}; + return this.parseArrowExpression(node, params, true); } } - // if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow)) - return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id]); + return this.parseArrowExpression(node, [id]); return id; @@ -423,67 +414,63 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow startPos = startPos || this.start; startLoc = startLoc || this.startLoc; let val; - if (this.options.ecmaVersion >= 6) { - this.next(); + this.next(); - if ((this.options.features["es7.comprehensions"] || this.options.ecmaVersion >= 7) && this.type === tt._for) { - return this.parseComprehension(this.startNodeAt(startPos, startLoc), true); - } + if (this.options.features["es7.comprehensions"] && this.type === tt._for) { + return this.parseComprehension(this.startNodeAt(startPos, startLoc), true); + } - let innerStartPos = this.start, innerStartLoc = this.startLoc; - let exprList = [], first = true; - let refShorthandDefaultPos = {start: 0}, spreadStart, innerParenStart, optionalCommaStart; - while (this.type !== tt.parenR) { - if (first) { - first = false; - } else { - this.expect(tt.comma); - if (this.type === tt.parenR && this.options.features["es7.trailingFunctionCommas"]) { - optionalCommaStart = this.start; - break; - } - } - - if (this.type === tt.ellipsis) { - let spreadNodeStartPos = this.start, spreadNodeStartLoc = this.startLoc; - spreadStart = this.start; - exprList.push(this.parseParenItem(this.parseRest(), spreadNodeStartLoc, spreadNodeStartPos)); - break; - } else { - if (this.type === tt.parenL && !innerParenStart) { - innerParenStart = this.start; - } - exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem)); - } - } - let innerEndPos = this.start, innerEndLoc = this.startLoc; - this.expect(tt.parenR); - - if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow)) { - if (innerParenStart) this.unexpected(innerParenStart); - return this.parseParenArrowList(startPos, startLoc, exprList, isAsync); - } - - if (!exprList.length) { - if (isAsync) { - return; - } else { - this.unexpected(this.lastTokStart); - } - } - if (optionalCommaStart) this.unexpected(optionalCommaStart); - if (spreadStart) this.unexpected(spreadStart); - if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start); - - if (exprList.length > 1) { - val = this.startNodeAt(innerStartPos, innerStartLoc); - val.expressions = exprList; - this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc); + let innerStartPos = this.start, innerStartLoc = this.startLoc; + let exprList = [], first = true; + let refShorthandDefaultPos = {start: 0}, spreadStart, innerParenStart, optionalCommaStart; + while (this.type !== tt.parenR) { + if (first) { + first = false; } else { - val = exprList[0]; + this.expect(tt.comma); + if (this.type === tt.parenR && this.options.features["es7.trailingFunctionCommas"]) { + optionalCommaStart = this.start; + break; + } } + + if (this.type === tt.ellipsis) { + let spreadNodeStartPos = this.start, spreadNodeStartLoc = this.startLoc; + spreadStart = this.start; + exprList.push(this.parseParenItem(this.parseRest(), spreadNodeStartLoc, spreadNodeStartPos)); + break; + } else { + if (this.type === tt.parenL && !innerParenStart) { + innerParenStart = this.start; + } + exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem)); + } + } + let innerEndPos = this.start, innerEndLoc = this.startLoc; + this.expect(tt.parenR); + + if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow)) { + if (innerParenStart) this.unexpected(innerParenStart); + return this.parseParenArrowList(startPos, startLoc, exprList, isAsync); + } + + if (!exprList.length) { + if (isAsync) { + return; + } else { + this.unexpected(this.lastTokStart); + } + } + if (optionalCommaStart) this.unexpected(optionalCommaStart); + if (spreadStart) this.unexpected(spreadStart); + if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start); + + if (exprList.length > 1) { + val = this.startNodeAt(innerStartPos, innerStartLoc); + val.expressions = exprList; + this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc); } else { - val = this.parseParenExpression(); + val = exprList[0]; } val.parenthesizedExpression = true; @@ -778,8 +765,7 @@ pp.parseIdent = function (liberal) { if (this.type === tt.name) { if (!liberal && ((!this.options.allowReserved && this.isReservedWord(this.value)) || - (this.strict && reservedWords.strict(this.value)) && - this.input.slice(this.start, this.end).indexOf("\\") === -1)) + (this.strict && reservedWords.strict(this.value)))) this.raise(this.start, "The keyword '" + this.value + "' is reserved"); node.name = this.value; } else if (liberal && this.type.keyword) { diff --git a/src/lookahead.js b/src/lookahead.js index 5c1718e45e..41b2cfe6eb 100644 --- a/src/lookahead.js +++ b/src/lookahead.js @@ -37,12 +37,18 @@ pp.getState = function () { return state; }; +pp.setState = function (state) { + for (var key in state) { + this[key] = state[key]; + } +}; + pp.lookahead = function () { var old = this.getState(); this.isLookahead = true; this.next(); this.isLookahead = false; var curr = this.getState(); - for (var key in old) this[key] = old[key]; + this.setState(old); return curr; }; diff --git a/src/plugins/flow.js b/src/plugins/flow.js index 7d9464f3d0..36e2b53b07 100644 --- a/src/plugins/flow.js +++ b/src/plugins/flow.js @@ -596,7 +596,9 @@ export default function (instance) { // function name(): string {} instance.extend("parseFunctionBody", function (inner) { return function (node, allowExpression) { - if (this.type === tt.colon) { + if (this.type === tt.colon && !allowExpression) { + // if allowExpression is true then we're parsing an arrow function and if + // there's a return type then it's been handled elsewhere node.returnType = this.flowParseTypeAnnotation(); } @@ -644,12 +646,24 @@ export default function (instance) { }); instance.extend("parseParenItem", function () { - return function (node, startLoc, startPos) { + return function (node, startLoc, startPos, forceArrow?) { if (this.type === tt.colon) { var typeCastNode = this.startNodeAt(startLoc, startPos); typeCastNode.expression = node; typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); - return this.finishNode(typeCastNode, "TypeCastExpression"); + + if (forceArrow && this.type !== tt.arrow) { + this.unexpected(); + } + + if (this.eat(tt.arrow)) { + // ((lol): number => {}); + var func = this.parseArrowExpression(this.startNodeAt(startLoc, startPos), [node]); + func.returnType = typeCastNode.typeAnnotation; + return func; + } else { + return this.finishNode(typeCastNode, "TypeCastExpression"); + } } else { return node; } @@ -819,4 +833,52 @@ export default function (instance) { } }; }); + + // var foo = (async (): number => {}); + instance.extend("parseAsyncArrowFromCallExpression", function (inner) { + return function (node, call) { + if (this.type === tt.colon) { + node.returnType = this.flowParseTypeAnnotation(); + } + + return inner.call(this, node, call); + }; + }); + + instance.extend("parseParenAndDistinguishExpression", function (inner) { + return function (startPos, startLoc, canBeArrow, isAsync) { + if (this.lookahead().type === tt.parenR) { + // var foo = (): number => {}; + this.expect(tt.parenL); + this.expect(tt.parenR); + + let node = this.startNodeAt(startPos, startLoc); + if (this.type === tt.colon) node.returnType = this.flowParseTypeAnnotation(); + this.expect(tt.arrow); + return this.parseArrowExpression(node, [], isAsync); + } else { + // var foo = (foo): number => {}; + startPos = startPos || this.start; + startLoc = startLoc || this.startLoc; + let node = inner.call(this, startPos, startLoc, canBeArrow, isAsync); + + var state = this.getState(); + + if (this.type === tt.colon) { + try { + return this.parseParenItem(node, startPos, startLoc, true); + } catch (err) { + if (err instanceof SyntaxError) { + this.setState(state); + return node; + } else { + throw err; + } + } + } else { + return node; + } + } + }; + }); } diff --git a/test/tests-flow.js b/test/tests-flow.js index f630e8955e..e1d2dc2dbe 100644 --- a/test/tests-flow.js +++ b/test/tests-flow.js @@ -5879,6 +5879,623 @@ var fbTestFixture = { } } }, + "var foo = (): number => bar;": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 28, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 27, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: null, + end: 27, + returnType: { + type: "TypeAnnotation", + start: 12, + end: 20, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 14, + end: 20 + } + }, + id: null, + generator: false, + expression: true, + params: [], + body: { + type: "Identifier", + start: 24, + end: 27, + name: "bar" + } + } + }] + }, + "var foo = (bar): number => bar;": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 31, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 30, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: 10, + end: 30, + id: null, + generator: false, + expression: true, + params: [ + { + type: "Identifier", + start: 11, + end: 14, + name: "bar", + parenthesizedExpression: true + } + ], + body: { + type: "Identifier", + start: 27, + end: 30, + name: "bar" + }, + returnType: { + type: "TypeAnnotation", + start: 15, + end: 23, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 17, + end: 23 + } + } + } + }] + }, + "var foo = async (): number => bar;": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 34, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 33, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: 10, + end: 33, + returnType: { + type: "TypeAnnotation", + start: 18, + end: 26, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 20, + end: 26 + } + }, + id: null, + generator: false, + expression: true, + async: true, + params: [], + body: { + type: "Identifier", + start: 30, + end: 33, + name: "bar" + } + } + }] + }, + "var foo = async (bar): number => bar;": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 37, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 36, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: 10, + end: 36, + returnType: { + type: "TypeAnnotation", + start: 21, + end: 29, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 23, + end: 29 + } + }, + id: null, + generator: false, + expression: true, + async: true, + params: [ + { + type: "Identifier", + start: 17, + end: 20, + name: "bar" + } + ], + body: { + type: "Identifier", + start: 33, + end: 36, + name: "bar" + } + } + } + ] + }, + "var foo = ((): number => bar);": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 30, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 29, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: null, + end: 28, + returnType: { + type: "TypeAnnotation", + start: 13, + end: 21, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 15, + end: 21 + } + }, + id: null, + generator: false, + expression: true, + params: [], + body: { + type: "Identifier", + start: 25, + end: 28, + name: "bar" + }, + parenthesizedExpression: true + } + }] + }, + "var foo = ((bar): number => bar);": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 33, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 32, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: 11, + end: 31, + id: null, + generator: false, + expression: true, + params: [ + { + type: "Identifier", + start: 12, + end: 15, + name: "bar", + parenthesizedExpression: true + } + ], + body: { + type: "Identifier", + start: 28, + end: 31, + name: "bar" + }, + returnType: { + type: "TypeAnnotation", + start: 16, + end: 24, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 18, + end: 24 + } + }, + parenthesizedExpression: true + } + }] + }, + "var foo = (((bar): number => bar): number);": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 43, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 42, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "TypeCastExpression", + start: 11, + end: 41, + expression: { + type: "ArrowFunctionExpression", + start: 12, + end: 32, + id: null, + generator: false, + expression: true, + params: [ + { + type: "Identifier", + start: 13, + end: 16, + name: "bar", + parenthesizedExpression: true + } + ], + body: { + type: "Identifier", + start: 29, + end: 32, + name: "bar" + }, + returnType: { + type: "TypeAnnotation", + start: 17, + end: 25, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 19, + end: 25 + } + }, + parenthesizedExpression: true + }, + typeAnnotation: { + type: "TypeAnnotation", + start: 33, + end: 41, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 35, + end: 41 + } + }, + parenthesizedExpression: true + } + }] + }, + "var foo = (async (): number => bar);": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 36, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 35, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: 11, + end: 34, + returnType: { + type: "TypeAnnotation", + start: 19, + end: 27, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 21, + end: 27 + } + }, + id: null, + generator: false, + expression: true, + async: true, + params: [], + body: { + type: "Identifier", + start: 31, + end: 34, + name: "bar" + }, + parenthesizedExpression: true + } + }] + }, + "var foo = (async (bar): number => bar);": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 39, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 38, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ArrowFunctionExpression", + start: 11, + end: 37, + returnType: { + type: "TypeAnnotation", + start: 22, + end: 30, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 24, + end: 30 + } + }, + id: null, + generator: false, + expression: true, + async: true, + params: [ + { + type: "Identifier", + start: 18, + end: 21, + name: "bar" + } + ], + body: { + type: "Identifier", + start: 34, + end: 37, + name: "bar" + }, + parenthesizedExpression: true + } + }], + }, + "var foo = ((async (bar): number => bar): number);": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 49, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 48, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "TypeCastExpression", + start: 11, + end: 47, + expression: { + type: "ArrowFunctionExpression", + start: 12, + end: 38, + returnType: { + type: "TypeAnnotation", + start: 23, + end: 31, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 25, + end: 31 + } + }, + id: null, + generator: false, + expression: true, + async: true, + params: [ + { + type: "Identifier", + start: 19, + end: 22, + name: "bar" + } + ], + body: { + type: "Identifier", + start: 35, + end: 38, + name: "bar" + }, + parenthesizedExpression: true + }, + typeAnnotation: { + type: "TypeAnnotation", + start: 39, + end: 47, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 41, + end: 47 + } + }, + parenthesizedExpression: true + } + }] + }, + "var foo = bar ? (foo) : number;": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 31, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 30, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ConditionalExpression", + start: 10, + end: 30, + test: { + type: "Identifier", + start: 10, + end: 13, + name: "bar" + }, + consequent: { + type: "Identifier", + start: 17, + end: 20, + name: "foo", + parenthesizedExpression: true + }, + alternate: { + type: "Identifier", + start: 24, + end: 30, + name: "number" + } + } + }] + }, + "var foo = bar ? (foo) : number => {} : baz;": { + type: "VariableDeclaration", + kind: "var", + start: 0, + end: 43, + declarations: [{ + type: "VariableDeclarator", + start: 4, + end: 42, + id: { + type: "Identifier", + start: 4, + end: 7, + name: "foo" + }, + init: { + type: "ConditionalExpression", + start: 10, + end: 42, + test: { + type: "Identifier", + start: 10, + end: 13, + name: "bar" + }, + consequent: { + type: "ArrowFunctionExpression", + start: 16, + end: 36, + id: null, + generator: false, + expression: false, + params: [ + { + type: "Identifier", + start: 17, + end: 20, + name: "foo", + parenthesizedExpression: true + } + ], + body: { + type: "BlockStatement", + start: 34, + end: 36, + body: [] + }, + returnType: { + type: "TypeAnnotation", + start: 22, + end: 30, + typeAnnotation: { + type: "NumberTypeAnnotation", + start: 24, + end: 30 + } + } + }, + alternate: { + type: "Identifier", + start: 39, + end: 42, + name: "baz" + } + } + }] + }, "((...rest: Array) => rest)": { type: "ExpressionStatement", start: 0, @@ -11370,7 +11987,6 @@ for (var ns in fbTestFixture) { type: "Program", body: [ns[code]] }, { - ecmaVersion: 7, sourceType: "module", plugins: { jsx: true, flow: true }, features: { "es7.asyncFunctions": true }, @@ -11381,7 +11997,6 @@ for (var ns in fbTestFixture) { } test("", {}, { - ecmaVersion: 6, sourceType: "module", plugins: { jsx: true, flow: true }, });