refactor: simplify toAssignable routine (#11032)

* refactor: remove isBinding parameter

* remove unused contextDescription

* refactor: remove unnecessary nullish check

* refactor: simplify toAssignable on ParenthesizedExpression

* tests: categorize createParenthesizedExpression tests
This commit is contained in:
Huáng Jùnliàng 2020-01-20 15:04:16 -05:00 committed by Nicolò Ribaudo
parent 9bc04baeb5
commit 43b23e0869
42 changed files with 235 additions and 163 deletions

View File

@ -212,7 +212,7 @@ export default class ExpressionParser extends LValParser {
this.expectPlugin("logicalAssignment"); this.expectPlugin("logicalAssignment");
} }
if (this.match(tt.eq)) { if (this.match(tt.eq)) {
node.left = this.toAssignable(left, undefined, "assignment expression"); node.left = this.toAssignable(left);
refExpressionErrors.doubleProto = -1; // reset because double __proto__ is valid in assignment expression refExpressionErrors.doubleProto = -1; // reset because double __proto__ is valid in assignment expression
} else { } else {
node.left = left; node.left = left;
@ -1857,15 +1857,17 @@ export default class ExpressionParser extends LValParser {
): N.ArrowFunctionExpression { ): N.ArrowFunctionExpression {
this.scope.enter(functionFlags(isAsync, false) | SCOPE_ARROW); this.scope.enter(functionFlags(isAsync, false) | SCOPE_ARROW);
this.initFunction(node, isAsync); this.initFunction(node, isAsync);
const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; const oldMaybeInArrowParameters = this.state.maybeInArrowParameters;
const oldYieldPos = this.state.yieldPos; const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
if (params) {
this.state.maybeInArrowParameters = true;
this.setArrowFunctionParameters(node, params, trailingCommaPos);
}
this.state.maybeInArrowParameters = false; this.state.maybeInArrowParameters = false;
this.state.yieldPos = -1; this.state.yieldPos = -1;
this.state.awaitPos = -1; this.state.awaitPos = -1;
if (params) this.setArrowFunctionParameters(node, params, trailingCommaPos);
this.parseFunctionBody(node, true); this.parseFunctionBody(node, true);
this.scope.exit(); this.scope.exit();
@ -1881,12 +1883,7 @@ export default class ExpressionParser extends LValParser {
params: N.Expression[], params: N.Expression[],
trailingCommaPos: ?number, trailingCommaPos: ?number,
): void { ): void {
node.params = this.toAssignableList( node.params = this.toAssignableList(params, trailingCommaPos);
params,
true,
"arrow function parameters",
trailingCommaPos,
);
} }
parseFunctionBodyAndFinish( parseFunctionBodyAndFinish(

View File

@ -50,114 +50,89 @@ export default class LValParser extends NodeUtils {
// NOTE: There is a corresponding "isAssignable" method in flow.js. // NOTE: There is a corresponding "isAssignable" method in flow.js.
// When this one is updated, please check if also that one needs to be updated. // When this one is updated, please check if also that one needs to be updated.
toAssignable( toAssignable(node: Node): Node {
node: Node, let parenthesized = undefined;
isBinding: ?boolean, if (node.type === "ParenthesizedExpression" || node.extra?.parenthesized) {
contextDescription: string, parenthesized = unwrapParenthesizedExpression(node);
): Node {
if (node) {
if ( if (
(this.options.createParenthesizedExpressions && parenthesized.type !== "Identifier" &&
node.type === "ParenthesizedExpression") || parenthesized.type !== "MemberExpression"
node.extra?.parenthesized
) { ) {
const parenthesized = unwrapParenthesizedExpression(node); this.raise(node.start, "Invalid parenthesized assignment pattern");
if ( }
parenthesized.type !== "Identifier" && }
parenthesized.type !== "MemberExpression"
switch (node.type) {
case "Identifier":
case "ObjectPattern":
case "ArrayPattern":
case "AssignmentPattern":
break;
case "ObjectExpression":
node.type = "ObjectPattern";
for (
let i = 0, length = node.properties.length, last = length - 1;
i < length;
i++
) { ) {
this.raise(node.start, "Invalid parenthesized assignment pattern"); const prop = node.properties[i];
} const isLast = i === last;
} this.toAssignableObjectExpressionProp(prop, isLast);
switch (node.type) { if (
case "Identifier": isLast &&
case "ObjectPattern": prop.type === "RestElement" &&
case "ArrayPattern": node.extra?.trailingComma
case "AssignmentPattern":
break;
case "ObjectExpression":
node.type = "ObjectPattern";
for (
let i = 0, length = node.properties.length, last = length - 1;
i < length;
i++
) { ) {
const prop = node.properties[i]; this.raiseRestNotLast(node.extra.trailingComma);
const isLast = i === last;
this.toAssignableObjectExpressionProp(prop, isBinding, isLast);
if (
isLast &&
prop.type === "RestElement" &&
node.extra?.trailingComma
) {
this.raiseRestNotLast(node.extra.trailingComma);
}
} }
break; }
break;
case "ObjectProperty": case "ObjectProperty":
this.toAssignable(node.value, isBinding, contextDescription); this.toAssignable(node.value);
break; break;
case "SpreadElement": { case "SpreadElement": {
this.checkToRestConversion(node); this.checkToRestConversion(node);
node.type = "RestElement"; node.type = "RestElement";
const arg = node.argument; const arg = node.argument;
this.toAssignable(arg, isBinding, contextDescription); this.toAssignable(arg);
break; break;
}
case "ArrayExpression":
node.type = "ArrayPattern";
this.toAssignableList(node.elements, node.extra?.trailingComma);
break;
case "AssignmentExpression":
if (node.operator !== "=") {
this.raise(
node.left.end,
"Only '=' operator can be used for specifying default value.",
);
} }
case "ArrayExpression": node.type = "AssignmentPattern";
node.type = "ArrayPattern"; delete node.operator;
this.toAssignableList( this.toAssignable(node.left);
node.elements, break;
isBinding,
contextDescription,
node.extra?.trailingComma,
);
break;
case "AssignmentExpression": case "ParenthesizedExpression":
if (node.operator !== "=") { this.toAssignable(((parenthesized: any): Expression));
this.raise( break;
node.left.end,
"Only '=' operator can be used for specifying default value.",
);
}
node.type = "AssignmentPattern"; default:
delete node.operator; // We don't know how to deal with this node. It will
this.toAssignable(node.left, isBinding, contextDescription); // be reported by a later call to checkLVal
break;
case "ParenthesizedExpression":
node.expression = this.toAssignable(
node.expression,
isBinding,
contextDescription,
);
break;
case "MemberExpression":
if (!isBinding) break;
default:
// We don't know how to deal with this node. It will
// be reported by a later call to checkLVal
}
} }
return node; return node;
} }
toAssignableObjectExpressionProp( toAssignableObjectExpressionProp(prop: Node, isLast: boolean) {
prop: Node,
isBinding: ?boolean,
isLast: boolean,
) {
if (prop.type === "ObjectMethod") { if (prop.type === "ObjectMethod") {
const error = const error =
prop.kind === "get" || prop.kind === "set" prop.kind === "get" || prop.kind === "set"
@ -168,7 +143,7 @@ export default class LValParser extends NodeUtils {
} else if (prop.type === "SpreadElement" && !isLast) { } else if (prop.type === "SpreadElement" && !isLast) {
this.raiseRestNotLast(prop.start); this.raiseRestNotLast(prop.start);
} else { } else {
this.toAssignable(prop, isBinding, "object destructuring pattern"); this.toAssignable(prop);
} }
} }
@ -176,8 +151,6 @@ export default class LValParser extends NodeUtils {
toAssignableList( toAssignableList(
exprList: Expression[], exprList: Expression[],
isBinding: ?boolean,
contextDescription: string,
trailingCommaPos?: ?number, trailingCommaPos?: ?number,
): $ReadOnlyArray<Pattern> { ): $ReadOnlyArray<Pattern> {
let end = exprList.length; let end = exprList.length;
@ -188,7 +161,7 @@ export default class LValParser extends NodeUtils {
} else if (last && last.type === "SpreadElement") { } else if (last && last.type === "SpreadElement") {
last.type = "RestElement"; last.type = "RestElement";
const arg = last.argument; const arg = last.argument;
this.toAssignable(arg, isBinding, contextDescription); this.toAssignable(arg);
if ( if (
arg.type !== "Identifier" && arg.type !== "Identifier" &&
arg.type !== "MemberExpression" && arg.type !== "MemberExpression" &&
@ -208,7 +181,7 @@ export default class LValParser extends NodeUtils {
for (let i = 0; i < end; i++) { for (let i = 0; i < end; i++) {
const elt = exprList[i]; const elt = exprList[i];
if (elt) { if (elt) {
this.toAssignable(elt, isBinding, contextDescription); this.toAssignable(elt);
if (elt.type === "RestElement") { if (elt.type === "RestElement") {
this.raiseRestNotLast(elt.start); this.raiseRestNotLast(elt.start);
} }

View File

@ -537,10 +537,10 @@ export default class StatementParser extends ExpressionParser {
const refExpressionErrors = new ExpressionErrors(); const refExpressionErrors = new ExpressionErrors();
const init = this.parseExpression(true, refExpressionErrors); const init = this.parseExpression(true, refExpressionErrors);
if (this.match(tt._in) || this.isContextual("of")) { if (this.match(tt._in) || this.isContextual("of")) {
this.toAssignable(init);
const description = this.isContextual("of") const description = this.isContextual("of")
? "for-of statement" ? "for-of statement"
: "for-in statement"; : "for-in statement";
this.toAssignable(init, undefined, description);
this.checkLVal(init, undefined, undefined, description); this.checkLVal(init, undefined, undefined, description);
return this.parseForIn(node, init, awaitAt); return this.parseForIn(node, init, awaitAt);
} else { } else {

View File

@ -362,25 +362,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return (node: any); return (node: any);
} }
toAssignable( toAssignable(node: N.Node): N.Node {
node: N.Node,
isBinding: ?boolean,
contextDescription: string,
): N.Node {
if (isSimpleProperty(node)) { if (isSimpleProperty(node)) {
this.toAssignable(node.value, isBinding, contextDescription); this.toAssignable(node.value);
return node; return node;
} }
return super.toAssignable(node, isBinding, contextDescription); return super.toAssignable(node);
} }
toAssignableObjectExpressionProp( toAssignableObjectExpressionProp(prop: N.Node, isLast: boolean) {
prop: N.Node,
isBinding: ?boolean,
isLast: boolean,
) {
if (prop.kind === "get" || prop.kind === "set") { if (prop.kind === "get" || prop.kind === "set") {
throw this.raise( throw this.raise(
prop.key.start, prop.key.start,
@ -392,7 +384,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
"Object pattern can't contain methods", "Object pattern can't contain methods",
); );
} else { } else {
super.toAssignableObjectExpressionProp(prop, isBinding, isLast); super.toAssignableObjectExpressionProp(prop, isLast);
} }
} }

View File

@ -1886,8 +1886,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// node.params is Expression[] instead of $ReadOnlyArray<Pattern> because it // node.params is Expression[] instead of $ReadOnlyArray<Pattern> because it
// has not been converted yet. // has not been converted yet.
((node.params: any): N.Expression[]), ((node.params: any): N.Expression[]),
true,
"arrow function parameters",
node.extra?.trailingComma, node.extra?.trailingComma,
); );
// Enter scope, as checkParams defines bindings // Enter scope, as checkParams defines bindings
@ -2091,27 +2089,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
} }
toAssignable( toAssignable(node: N.Node): N.Node {
node: N.Node,
isBinding: ?boolean,
contextDescription: string,
): N.Node {
if (node.type === "TypeCastExpression") { if (node.type === "TypeCastExpression") {
return super.toAssignable( return super.toAssignable(this.typeCastToParameter(node));
this.typeCastToParameter(node),
isBinding,
contextDescription,
);
} else { } else {
return super.toAssignable(node, isBinding, contextDescription); return super.toAssignable(node);
} }
} }
// turn type casts that we found in function parameter head into type annotated params // turn type casts that we found in function parameter head into type annotated params
toAssignableList( toAssignableList(
exprList: N.Expression[], exprList: N.Expression[],
isBinding: ?boolean,
contextDescription: string,
trailingCommaPos?: ?number, trailingCommaPos?: ?number,
): $ReadOnlyArray<N.Pattern> { ): $ReadOnlyArray<N.Pattern> {
for (let i = 0; i < exprList.length; i++) { for (let i = 0; i < exprList.length; i++) {
@ -2120,12 +2108,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
exprList[i] = this.typeCastToParameter(expr); exprList[i] = this.typeCastToParameter(expr);
} }
} }
return super.toAssignableList( return super.toAssignableList(exprList, trailingCommaPos);
exprList,
isBinding,
contextDescription,
trailingCommaPos,
);
} }
// this is a list of nodes, from something like a call expression, we need to filter the // this is a list of nodes, from something like a call expression, we need to filter the

View File

@ -2392,31 +2392,19 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return param; return param;
} }
toAssignable( toAssignable(node: N.Node): N.Node {
node: N.Node,
isBinding: ?boolean,
contextDescription: string,
): N.Node {
switch (node.type) { switch (node.type) {
case "TSTypeCastExpression": case "TSTypeCastExpression":
return super.toAssignable( return super.toAssignable(this.typeCastToParameter(node));
this.typeCastToParameter(node),
isBinding,
contextDescription,
);
case "TSParameterProperty": case "TSParameterProperty":
return super.toAssignable(node, isBinding, contextDescription); return super.toAssignable(node);
case "TSAsExpression": case "TSAsExpression":
case "TSNonNullExpression": case "TSNonNullExpression":
case "TSTypeAssertion": case "TSTypeAssertion":
node.expression = this.toAssignable( node.expression = this.toAssignable(node.expression);
node.expression,
isBinding,
contextDescription,
);
return node; return node;
default: default:
return super.toAssignable(node, isBinding, contextDescription); return super.toAssignable(node);
} }
} }
@ -2524,10 +2512,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
} }
} }
toAssignableList( toAssignableList(exprList: N.Expression[]): $ReadOnlyArray<N.Pattern> {
exprList: N.Expression[],
isBinding: ?boolean,
): $ReadOnlyArray<N.Pattern> {
for (let i = 0; i < exprList.length; i++) { for (let i = 0; i < exprList.length; i++) {
const expr = exprList[i]; const expr = exprList[i];
if (!expr) continue; if (!expr) continue;
@ -2537,7 +2522,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
break; break;
case "TSAsExpression": case "TSAsExpression":
case "TSTypeAssertion": case "TSTypeAssertion":
if (!isBinding) { if (!this.state.maybeInArrowParameters) {
exprList[i] = this.typeCastToParameter(expr); exprList[i] = this.typeCastToParameter(expr);
} else { } else {
this.raise( this.raise(

View File

@ -0,0 +1,138 @@
{
"type": "File",
"start": 0,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 9
}
},
"errors": [
"SyntaxError: Invalid left-hand side in parenthesized expression (1:1)"
],
"program": {
"type": "Program",
"start": 0,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 9
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 9
}
},
"expression": {
"type": "AssignmentExpression",
"start": 0,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 9
}
},
"operator": "+=",
"left": {
"type": "ParenthesizedExpression",
"start": 0,
"end": 4,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 4
}
},
"expression": {
"type": "UnaryExpression",
"start": 1,
"end": 3,
"loc": {
"start": {
"line": 1,
"column": 1
},
"end": {
"line": 1,
"column": 3
}
},
"operator": "!",
"prefix": true,
"argument": {
"type": "Identifier",
"start": 2,
"end": 3,
"loc": {
"start": {
"line": 1,
"column": 2
},
"end": {
"line": 1,
"column": 3
},
"identifierName": "a"
},
"name": "a"
}
}
},
"right": {
"type": "NumericLiteral",
"start": 8,
"end": 9,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 9
}
},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,3 @@
{
"createParenthesizedExpressions": true
}