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

Commit

Permalink
Use state instead of relying on the "noArrowParamsConversion" parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Jun 25, 2017
1 parent 2f60733 commit 74ef24e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 37 deletions.
64 changes: 33 additions & 31 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -1057,17 +1057,20 @@ export default class ExpressionParser extends LValParser {

parseArrowExpression(node: N.ArrowFunctionExpression, params: N.Expression[], isAsync?: boolean, noArrowParamsConversion?: boolean): N.ArrowFunctionExpression {
this.initFunction(node, isAsync);

if (noArrowParamsConversion) {
node.params = params;
} else {
node.params = this.toAssignableList(params, true, "arrow function parameters");
}

this.setArrowFunctionParameters(node, params, noArrowParamsConversion);
this.parseFunctionBody(node, true, noArrowParamsConversion);
return this.finishNode(node, "ArrowFunctionExpression");
}

setArrowFunctionParameters(
node: N.ArrowFunctionExpression,
params: N.Expression[],
// eslint-disable-next-line no-unused-vars
noArrowParamsConversion?: boolean
): N.ArrowFunctionExpression {
node.params = this.toAssignableList(params, true, "arrow function parameters");
}

isStrictBody(node: { body: N.BlockStatement }, isExpression?: boolean): boolean {
if (!isExpression && node.body.directives.length) {
for (const directive of node.body.directives) {
Expand Down Expand Up @@ -1102,41 +1105,40 @@ export default class ExpressionParser extends LValParser {
}
this.state.inAsync = oldInAsync;

this.checkFunctionNameAndParams(node, allowExpression);
}

checkFunctionNameAndParams(
node: N.Function,
isArrowFunction?: boolean
): void {
// If this is a strict mode function, verify that argument names
// are not repeated, and it does not try to bind the words `eval`
// or `arguments`.
const isStrict = this.isStrictBody(node, isExpression);
const isStrict = this.isStrictBody(node, node.expression);
const checkLVal = this.state.strict || isStrict || isArrowFunction;

if (isStrict && node.id && node.id.type === "Identifier" && node.id.name === "yield") {
this.raise(node.id.start, "Binding yield in strict mode");
}

// If this is an arrow function, check its parameter only if they are
// converted to assignable nodes
if (allowExpression ? !noArrowParamsConversion : this.state.strict || isStrict) {
this.checkFunctionLValues(node);
}
}

// Checks that functions parameters and function name (if it exists) are valid
// left hand side values.
checkFunctionLValues(node: N.Function): void {
const nameHash = Object.create(null);
const oldStrict = this.state.strict;
const isStrict = this.isStrictBody(node, node.expression);
if (checkLVal) {
const nameHash = Object.create(null);
const oldStrict = this.state.strict;

if (isStrict) this.state.strict = true;
if (node.id) {
this.checkLVal(node.id, true, undefined, "function name");
}
for (const param of node.params) {
if (isStrict && param.type !== "Identifier") {
this.raise(param.start, "Non-simple parameter in strict mode");
if (isStrict) this.state.strict = true;
if (node.id) {
this.checkLVal(node.id, true, undefined, "function name");
}
for (const param of node.params) {
if (isStrict && param.type !== "Identifier") {
this.raise(param.start, "Non-simple parameter in strict mode");
}
this.checkLVal(param, true, nameHash, "function parameter list");
}
this.checkLVal(param, true, nameHash, "function parameter list");
}

this.state.strict = oldStrict;
this.state.strict = oldStrict;
}
}

// Parses a comma-separated list of expressions, and returns them as
Expand Down
56 changes: 50 additions & 6 deletions src/plugins/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -1016,9 +1016,15 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
// Overrides
// ==================================

// plain function return types: function name(): string {}
parseFunctionBody(node: N.Function, allowExpression?: boolean, noArrowParamsConversion?: boolean): void {
if (this.match(tt.colon) && !allowExpression) {
if (allowExpression && this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
this.state.noArrowParamsConversionAt.push(this.state.start);
super.parseFunctionBody(node, allowExpression);
this.state.noArrowParamsConversionAt.pop();

return;
} else if (this.match(tt.colon)) {
// plain function return types: function name(): string {}
// if allowExpression is true then we're parsing an arrow function and if
// there's a return type then it's been handled elsewhere
const typeNode = this.startNode();
Expand Down Expand Up @@ -1151,18 +1157,24 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super

node.test = expr;
node.consequent = consequent;
node.alternate = this.parseMaybeAssign(noIn, undefined, undefined, undefined, noArrowParamsConversion);
node.alternate = this.forwardNoArrowParamsConversionAt(node, () => (
this.parseMaybeAssign(noIn, undefined, undefined, undefined, noArrowParamsConversion)
));

return this.finishNode(node, "ConditionalExpression");
}

tryParseConditionalConsequent(): { consequent: N.Expression, failed: boolean } {
this.state.noArrowParamsConversionAt.push(this.state.start);

const consequent = this.parseMaybeAssign(
undefined, undefined, undefined, undefined,
/* noArrowParamsConversion */ true
);
const failed = !this.match(tt.colon);

this.state.noArrowParamsConversionAt.pop();

return { consequent, failed };
}

Expand All @@ -1183,7 +1195,8 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
if (node.typeParameters || !node.returnType) {
// This is an arrow expression without ambiguity, so check its parameters
this.toAssignableList(node.params, true, "arrow function parameters");
this.checkFunctionLValues(node);
// Use super's method to force the parameters to be checked
super.checkFunctionNameAndParams(node, true);
} else {
arrows.push(node);
}
Expand Down Expand Up @@ -1211,6 +1224,19 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
});
}

forwardNoArrowParamsConversionAt<T>(node: N.Node, parse: () => T): T {
let result: T;
if (this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
this.state.noArrowParamsConversionAt.push(this.state.start);
result = parse();
this.state.noArrowParamsConversionAt.pop(this.state.start);
} else {
result = parse();
}

return result;
}

parseParenItem(node: N.Expression, startPos: number, startLoc: Position): N.Expression {
node = super.parseParenItem(node, startPos, startLoc);
if (this.eat(tt.question)) {
Expand Down Expand Up @@ -1626,8 +1652,9 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
let typeParameters;
try {
typeParameters = this.flowParseTypeParameterDeclaration();

arrowExpression = super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos, noArrowParamsConversion);
arrowExpression = this.forwardNoArrowParamsConversionAt(typeParameters, () => (
super.parseMaybeAssign(noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos, noArrowParamsConversion)
));
arrowExpression.typeParameters = typeParameters;
this.resetStartLocationFromNode(arrowExpression, typeParameters);
} catch (err) {
Expand Down Expand Up @@ -1687,6 +1714,23 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
return this.match(tt.colon) || super.shouldParseArrow();
}

setArrowFunctionParameters(node: N.ArrowFunctionExpression, params: N.Expression[], noArrowParamsConversion ?: boolean): N.ArrowFunctionExpression {
if (this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
node.params = params;
return node;
}

return super.setArrowFunctionParameters(node, params);
}

checkFunctionNameAndParams(node: N.Function, isArrowFunction?: boolean): void {
if (isArrowFunction && this.state.noArrowParamsConversionAt.indexOf(node.start) !== -1) {
return;
}

return super.checkFunctionNameAndParams(node, isArrowFunction);
}

parseParenAndDistinguishExpression(canBeArrow: boolean, noArrowParamsConversion?: boolean): N.Expression {
return super.parseParenAndDistinguishExpression(
canBeArrow && this.state.noArrowAt.indexOf(this.state.start) === -1,
Expand Down
9 changes: 9 additions & 0 deletions src/tokenizer/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default class State {
this.potentialArrowAt = -1;

this.noArrowAt = [];
this.noArrowParamsConversionAt = [];

this.inMethod =
this.inFunction =
Expand Down Expand Up @@ -81,6 +82,14 @@ export default class State {
// ^
noArrowAt: number[];

// Used to signify the start of an expression whose params, if it looks like
// an arrow function, shouldn't be converted to assignable nodes.
// This is used to defer the validation of typed arrow functions inside
// conditional expressions.
// e.g. a ? (b) : c => d
// ^
noArrowParamsConversionAt: number[];

// Flags to track whether we are in a function, a generator.
inFunction: boolean;
inGenerator: boolean;
Expand Down

0 comments on commit 74ef24e

Please sign in to comment.