rewrite async function parsing, properly parse flow return types of arrow functions - fixes #1991

This commit is contained in:
Sebastian McKenzie 2015-07-15 20:08:10 +01:00
parent a8b8482326
commit 043d007285
4 changed files with 767 additions and 98 deletions

View File

@ -233,11 +233,19 @@ pp.parseSubscripts = function(base, startPos, startLoc, noCalls) {
node.computed = true; node.computed = true;
this.expect(tt.bracketR); this.expect(tt.bracketR);
base = this.finishNode(node, "MemberExpression"); 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); let node = this.startNodeAt(startPos, startLoc);
node.callee = base; node.callee = base;
node.arguments = this.parseExprList(tt.parenR, this.options.features["es7.trailingFunctionCommas"]); node.arguments = this.parseExprList(tt.parenR, this.options.features["es7.trailingFunctionCommas"]);
base = this.finishNode(node, "CallExpression"); 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) { } else if (this.type === tt.backQuote) {
let node = this.startNodeAt(startPos, startLoc); let node = this.startNodeAt(startPos, startLoc);
node.tag = base; 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). // Parse a no-call expression (like argument of `new` or `::` operators).
pp.parseNoCallExpr = function () { pp.parseNoCallExpr = function () {
@ -291,49 +305,26 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
} }
case tt.name: case tt.name:
let startPos = this.start, startLoc = this.startLoc;
node = this.startNode(); node = this.startNode();
let id = this.parseIdent(true); let id = this.parseIdent(true);
// //
if (this.options.features["es7.asyncFunctions"]) { if (this.options.features["es7.asyncFunctions"]) {
// async functions! if (id.name === "await") {
if (id.name === "async" && !this.canInsertSemicolon()) { if (this.inAsync) return this.parseAwait(node);
// arrow functions } else if (id.name === "async" && this.type === tt._function && !this.canInsertSemicolon()) {
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(); this.next();
return this.parseFunction(node, false, false, true); return this.parseFunction(node, false, false, true);
} } else if (id.name === "async" && this.type === tt.name) {
} else if (id.name === "await") { var params = [this.parseIdent()];
if (this.inAsync) return this.parseAwait(node); this.expect(tt.arrow);
// var foo = bar => {};
return this.parseArrowExpression(node, params, true);
} }
} }
//
if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow)) if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow))
return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id]); return this.parseArrowExpression(node, [id]);
return id; return id;
@ -423,10 +414,9 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow
startPos = startPos || this.start; startPos = startPos || this.start;
startLoc = startLoc || this.startLoc; startLoc = startLoc || this.startLoc;
let val; 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) { if (this.options.features["es7.comprehensions"] && this.type === tt._for) {
return this.parseComprehension(this.startNodeAt(startPos, startLoc), true); return this.parseComprehension(this.startNodeAt(startPos, startLoc), true);
} }
@ -482,9 +472,6 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow
} else { } else {
val = exprList[0]; val = exprList[0];
} }
} else {
val = this.parseParenExpression();
}
val.parenthesizedExpression = true; val.parenthesizedExpression = true;
return val; return val;
@ -778,8 +765,7 @@ pp.parseIdent = function (liberal) {
if (this.type === tt.name) { if (this.type === tt.name) {
if (!liberal && if (!liberal &&
((!this.options.allowReserved && this.isReservedWord(this.value)) || ((!this.options.allowReserved && this.isReservedWord(this.value)) ||
(this.strict && reservedWords.strict(this.value)) && (this.strict && reservedWords.strict(this.value))))
this.input.slice(this.start, this.end).indexOf("\\") === -1))
this.raise(this.start, "The keyword '" + this.value + "' is reserved"); this.raise(this.start, "The keyword '" + this.value + "' is reserved");
node.name = this.value; node.name = this.value;
} else if (liberal && this.type.keyword) { } else if (liberal && this.type.keyword) {

View File

@ -37,12 +37,18 @@ pp.getState = function () {
return state; return state;
}; };
pp.setState = function (state) {
for (var key in state) {
this[key] = state[key];
}
};
pp.lookahead = function () { pp.lookahead = function () {
var old = this.getState(); var old = this.getState();
this.isLookahead = true; this.isLookahead = true;
this.next(); this.next();
this.isLookahead = false; this.isLookahead = false;
var curr = this.getState(); var curr = this.getState();
for (var key in old) this[key] = old[key]; this.setState(old);
return curr; return curr;
}; };

View File

@ -596,7 +596,9 @@ export default function (instance) {
// function name(): string {} // function name(): string {}
instance.extend("parseFunctionBody", function (inner) { instance.extend("parseFunctionBody", function (inner) {
return function (node, allowExpression) { 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(); node.returnType = this.flowParseTypeAnnotation();
} }
@ -644,12 +646,24 @@ export default function (instance) {
}); });
instance.extend("parseParenItem", function () { instance.extend("parseParenItem", function () {
return function (node, startLoc, startPos) { return function (node, startLoc, startPos, forceArrow?) {
if (this.type === tt.colon) { if (this.type === tt.colon) {
var typeCastNode = this.startNodeAt(startLoc, startPos); var typeCastNode = this.startNodeAt(startLoc, startPos);
typeCastNode.expression = node; typeCastNode.expression = node;
typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); typeCastNode.typeAnnotation = this.flowParseTypeAnnotation();
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"); return this.finishNode(typeCastNode, "TypeCastExpression");
}
} else { } else {
return node; 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;
}
}
};
});
} }

View File

@ -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<number>) => rest)": { "((...rest: Array<number>) => rest)": {
type: "ExpressionStatement", type: "ExpressionStatement",
start: 0, start: 0,
@ -11370,7 +11987,6 @@ for (var ns in fbTestFixture) {
type: "Program", type: "Program",
body: [ns[code]] body: [ns[code]]
}, { }, {
ecmaVersion: 7,
sourceType: "module", sourceType: "module",
plugins: { jsx: true, flow: true }, plugins: { jsx: true, flow: true },
features: { "es7.asyncFunctions": true }, features: { "es7.asyncFunctions": true },
@ -11381,7 +11997,6 @@ for (var ns in fbTestFixture) {
} }
test("<Foo foo={function (): void {}} />", {}, { test("<Foo foo={function (): void {}} />", {}, {
ecmaVersion: 6,
sourceType: "module", sourceType: "module",
plugins: { jsx: true, flow: true }, plugins: { jsx: true, flow: true },
}); });