Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Parses flow type cast expression with parenthesis in call expression correctly #446

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 45 additions & 26 deletions src/parser/expression.js
Expand Up @@ -277,6 +277,31 @@ pp.parseExprSubscripts = function (refShorthandDefaultPos) {
return this.parseSubscripts(expr, startPos, startLoc);
};

pp.checkIsAsyncArrowExpression = function(base) {
if (
this.state.potentialArrowAt !== base.start ||
base.type !== "Identifier" ||
base.name !== "async" ||
this.canInsertSemicolon()
) {
return false;
}
const stack = [this.expect(tt.parenL)];
while (stack.length) {
if (this.eat(tt.parenL)) {
stack.push(this.state.type);
} else if (this.eat(tt.parenR)) {
stack.pop();
} else {
this.next();
}
}
if (this.match(tt.arrow)) {
return true;
}
return false;
};

pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
for (;;) {
if (!noCalls && this.eat(tt.doubleColon)) {
Expand All @@ -298,22 +323,23 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
this.expect(tt.bracketR);
base = this.finishNode(node, "MemberExpression");
} else if (!noCalls && this.match(tt.parenL)) {
const possibleAsync = this.state.potentialArrowAt === base.start && base.type === "Identifier" && base.name === "async" && !this.canInsertSemicolon();
const old = this.state.clone(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little bit worried about this and doing it whenever we encounter a parenL. clone() is very expensive and we have never done lookaheads of this extend before (besides one token with this.lookahead()).

if (this.checkIsAsyncArrowExpression(base)) {
this.state = old;
const node = this.startNodeAt(startPos, startLoc);
this.parseFunctionParams(node);
return this.parseAsyncArrowExpression(node, node.params);
}
this.state = old;
this.next();

const node = this.startNodeAt(startPos, startLoc);
node.callee = base;
node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync);
node.arguments = this.parseCallExpressionArguments(tt.parenR);
if (node.callee.type === "Import" && node.arguments.length !== 1) {
this.raise(node.start, "import() requires exactly one argument");
}
base = this.finishNode(node, "CallExpression");

if (possibleAsync && this.shouldParseAsyncArrow()) {
return this.parseAsyncArrowFromCallExpression(this.startNodeAt(startPos, startLoc), node);
} else {
this.toReferencedList(node.arguments);
}
this.toReferencedList(node.arguments);
} else if (this.match(tt.backQuote)) {
const node = this.startNodeAt(startPos, startLoc);
node.tag = base;
Expand All @@ -325,7 +351,7 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
}
};

pp.parseCallExpressionArguments = function (close, possibleAsyncArrow) {
pp.parseCallExpressionArguments = function (close) {
const elts = [];
let innerParenStart;
let first = true;
Expand All @@ -343,24 +369,15 @@ pp.parseCallExpressionArguments = function (close, possibleAsyncArrow) {
innerParenStart = this.state.start;
}

elts.push(this.parseExprListItem(false, possibleAsyncArrow ? { start: 0 } : undefined, possibleAsyncArrow ? { start: 0 } : undefined));
}

// we found an async arrow function so let's not allow any inner parens
if (possibleAsyncArrow && innerParenStart && this.shouldParseAsyncArrow()) {
this.unexpected();
elts.push(this.parseExprListItem(false));
}

return elts;
};

pp.shouldParseAsyncArrow = function () {
return this.match(tt.arrow);
};

pp.parseAsyncArrowFromCallExpression = function (node, call) {
pp.parseAsyncArrowExpression = function (node, args) {
this.expect(tt.arrow);
return this.parseArrowExpression(node, call.arguments, true);
return this.parseArrowExpression(node, args, true);
};

// Parse a no-call expression (like argument of `new` or `::` operators).
Expand Down Expand Up @@ -416,10 +433,10 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {

case tt.name:
node = this.startNode();

const allowAwait = this.state.value === "await" && this.state.inAsync;
const allowYield = this.shouldAllowYieldIdentifier();
const id = this.parseIdentifier(allowAwait || allowYield);

if (id.name === "await") {
if (this.state.inAsync || this.inModule) {
return this.parseAwait(node);
Expand All @@ -430,7 +447,7 @@ pp.parseExprAtom = function (refShorthandDefaultPos) {
} else if (canBeArrow && id.name === "async" && this.match(tt.name)) {
const params = [this.parseIdentifier()];
this.expect(tt.arrow);
// let foo = bar => {};
// let foo = async bar => {};
return this.parseArrowExpression(node, params, true);
}

Expand Down Expand Up @@ -1037,14 +1054,16 @@ pp.parseExprList = function (close, allowEmpty, refShorthandDefaultPos) {
return elts;
};

pp.parseExprListItem = function (allowEmpty, refShorthandDefaultPos, refNeedsArrowPos) {
pp.parseExprListItem = function (allowEmpty, refShorthandDefaultPos) {
let elt;
if (allowEmpty && this.match(tt.comma)) {
elt = null;
} else if (this.match(tt.ellipsis)) {
elt = this.parseSpread(refShorthandDefaultPos);
} else if (this.match(tt.parenL)) {
elt = this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem);
} else {
elt = this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem, refNeedsArrowPos);
elt = this.parseMaybeAssign(false, refShorthandDefaultPos, null);
}
return elt;
};
Expand Down
33 changes: 19 additions & 14 deletions src/plugins/flow.js
Expand Up @@ -949,7 +949,6 @@ export default function (instance) {
if (this.eat(tt.question)) {
node.optional = true;
}

if (this.match(tt.colon)) {
const typeCastNode = this.startNodeAt(startPos, startLoc);
typeCastNode.expression = node;
Expand Down Expand Up @@ -1077,17 +1076,13 @@ export default function (instance) {
};
});

// parse an item inside a expression list eg. `(NODE, NODE)` where NODE represents
// the position where this function is called
// parse an item inside a expression list, the type cast expression is expected to be
// wrraped with parentheses
instance.extend("parseExprListItem", function (inner) {
return function (...args) {
const container = this.startNode();
const node = inner.call(this, ...args);
if (this.match(tt.colon)) {
container._exprListItem = true;
container.expression = node;
container.typeAnnotation = this.flowParseTypeAnnotation();
return this.finishNode(container, "TypeCastExpression");
this.raise(node.start, "The type cast expression is expected to be wrapped with parenthesis");
} else {
return node;
}
Expand Down Expand Up @@ -1328,8 +1323,8 @@ export default function (instance) {
};
});

// parse the return type of an async arrow function - let foo = (async (): number => {});
instance.extend("parseAsyncArrowFromCallExpression", function (inner) {
// parse the return type of an async arrow function - let foo = async (): number => {};
instance.extend("parseAsyncArrowExpression", function (inner) {
return function (node, call) {
if (this.match(tt.colon)) {
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
Expand All @@ -1342,10 +1337,20 @@ export default function (instance) {
};
});

// todo description
instance.extend("shouldParseAsyncArrow", function (inner) {
return function () {
return this.match(tt.colon) || inner.call(this);
// check whether the expression is an async arrow function - let foo = async (): number => {};
instance.extend("checkIsAsyncArrowExpression", function (inner) {
return function (base) {
const previous = inner.call(this, base);
if (!previous) {
if (this.match(tt.colon)) {
const oldNoAnonFunctionType = this.state.noAnonFunctionType;
this.state.noAnonFunctionType = true;
this.flowParseTypeAnnotation();
this.state.noAnonFunctionType = oldNoAnonFunctionType;
return true;
}
}
return previous;
};
});

Expand Down
2 changes: 1 addition & 1 deletion src/tokenizer/types.js
Expand Up @@ -4,7 +4,7 @@
// allows the tokenizer to store the information it has about a
// token in a way that is very cheap for the parser to look up.

// All token type variables start with an underscore, to make them
// All keyword token type variables start with an underscore, to make them
// easy to recognize.

// The `beforeExpr` property is used to disambiguate between regular
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/es2017/async-functions/20/actual.js
@@ -1 +1 @@
var ok = async(x)
var ok = async(x)
2 changes: 1 addition & 1 deletion test/fixtures/es2017/async-functions/3/options.json
@@ -1,3 +1,3 @@
{
"throws": "Unexpected token (1:24)"
"throws": "Unexpected token (1:17)"
}
2 changes: 1 addition & 1 deletion test/fixtures/flow/typecasts/1/actual.js
@@ -1 +1 @@
(xxx: number)
(xxx: number)
1 change: 1 addition & 0 deletions test/fixtures/flow/typecasts/10/actual.js
@@ -0,0 +1 @@
method(aaa, [('xxx': string), bbb])