Skip to content

Commit

Permalink
Fix parsing ts type casts and nested patterns in destructuring (#14500)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed May 1, 2022
1 parent 4ce5740 commit 0e6900d
Show file tree
Hide file tree
Showing 41 changed files with 1,414 additions and 156 deletions.
6 changes: 4 additions & 2 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -303,7 +303,8 @@ export default class ExpressionParser extends LValParser {
node.operator = operator;

if (this.match(tt.eq)) {
node.left = this.toAssignable(left, /* isLHS */ true);
this.toAssignable(left, /* isLHS */ true);
node.left = left;

if (
refExpressionErrors.doubleProtoLoc != null &&
Expand Down Expand Up @@ -2425,7 +2426,8 @@ export default class ExpressionParser extends LValParser {
params: N.Expression[],
trailingCommaLoc: ?Position,
): void {
node.params = this.toAssignableList(params, trailingCommaLoc, false);
this.toAssignableList(params, trailingCommaLoc, false);
node.params = params;
}

parseFunctionBodyAndFinish(
Expand Down
124 changes: 65 additions & 59 deletions packages/babel-parser/src/parser/lval.js
Expand Up @@ -90,23 +90,25 @@ export default class LValParser extends NodeUtils {
When this one is updated, please check if also that one needs to be updated.
* @param {Node} node The expression atom
* @param {boolean} [isLHS=false] Whether we are parsing a LeftHandSideExpression. If isLHS is `true`, the following cases are allowed:
`[(a)] = [0]`, `[(a.b)] = [0]`
* @returns {Node} The converted assignable pattern
* @param {boolean} [isLHS=false] Whether we are parsing a LeftHandSideExpression.
* If isLHS is `true`, the following cases are allowed: `[(a)] = [0]`, `[(a.b)] = [0]`
* If isLHS is `false`, we are in an arrow function parameters list.
* @memberof LValParser
*/
toAssignable(node: Node, isLHS: boolean = false): Node {
toAssignable(node: Node, isLHS: boolean = false): void {
let parenthesized = undefined;
if (node.type === "ParenthesizedExpression" || node.extra?.parenthesized) {
parenthesized = unwrapParenthesizedExpression(node);
if (isLHS) {
// an LHS can be reinterpreted to a binding pattern but not vice versa.
// therefore a parenthesized identifier is ambiguous until we are sure it is an assignment expression
// i.e. `([(a) = []] = []) => {}`
// see also `recordParenthesizedIdentifierError` signature in packages/babel-parser/src/util/expression-scope.js
// see also `recordArrowParemeterBindingError` signature in packages/babel-parser/src/util/expression-scope.js
if (parenthesized.type === "Identifier") {
this.expressionScope.recordParenthesizedIdentifierError({ at: node });
this.expressionScope.recordArrowParemeterBindingError(
Errors.InvalidParenthesizedAssignment,
{ at: node },
);
} else if (parenthesized.type !== "MemberExpression") {
// A parenthesized member expression can be in LHS but not in pattern.
// If the LHS is later interpreted as a pattern, `checkLVal` will throw for member expression binding
Expand Down Expand Up @@ -162,12 +164,10 @@ export default class LValParser extends NodeUtils {
}

case "SpreadElement": {
this.checkToRestConversion(node);

node.type = "RestElement";
const arg = node.argument;
this.toAssignable(arg, isLHS);
break;
throw new Error(
"Internal @babel/parser error (this is a bug, please report it)." +
" SpreadElement should be converted by .toAssignable's caller.",
);
}

case "ArrayExpression":
Expand Down Expand Up @@ -198,7 +198,6 @@ export default class LValParser extends NodeUtils {
// We don't know how to deal with this node. It will
// be reported by a later call to checkLVal
}
return node;
}

toAssignableObjectExpressionProp(
Expand All @@ -213,8 +212,15 @@ export default class LValParser extends NodeUtils {
: Errors.PatternHasMethod,
{ at: prop.key },
);
} else if (prop.type === "SpreadElement" && !isLast) {
this.raise(Errors.RestTrailingComma, { at: prop });
} else if (prop.type === "SpreadElement") {
prop.type = "RestElement";
const arg = prop.argument;
this.checkToRestConversion(arg, /* allowPattern */ false);
this.toAssignable(arg, isLHS);

if (!isLast) {
this.raise(Errors.RestTrailingComma, { at: prop });
}
} else {
this.toAssignable(prop, isLHS);
}
Expand All @@ -226,43 +232,30 @@ export default class LValParser extends NodeUtils {
exprList: Expression[],
trailingCommaLoc?: ?Position,
isLHS: boolean,
): $ReadOnlyArray<Pattern> {
let end = exprList.length;
if (end) {
const last = exprList[end - 1];
if (last?.type === "RestElement") {
--end;
} else if (last?.type === "SpreadElement") {
last.type = "RestElement";
let arg = last.argument;
this.toAssignable(arg, isLHS);
arg = unwrapParenthesizedExpression(arg);
if (
arg.type !== "Identifier" &&
arg.type !== "MemberExpression" &&
arg.type !== "ArrayPattern" &&
arg.type !== "ObjectPattern"
) {
this.unexpected(arg.start);
}

if (trailingCommaLoc) {
this.raise(Errors.RestTrailingComma, { at: trailingCommaLoc });
}
): void {
const end = exprList.length - 1;

--end;
}
}
for (let i = 0; i < end; i++) {
for (let i = 0; i <= end; i++) {
const elt = exprList[i];
if (elt) {
if (!elt) continue;

if (elt.type === "SpreadElement") {
elt.type = "RestElement";
const arg = elt.argument;
this.checkToRestConversion(arg, /* allowPattern */ true);
this.toAssignable(arg, isLHS);
} else {
this.toAssignable(elt, isLHS);
if (elt.type === "RestElement") {
}

if (elt.type === "RestElement") {
if (i < end) {
this.raise(Errors.RestTrailingComma, { at: elt });
} else if (trailingCommaLoc) {
this.raise(Errors.RestTrailingComma, { at: trailingCommaLoc });
}
}
}
return exprList;
}

isAssignable(node: Node, isBinding?: boolean): boolean {
Expand Down Expand Up @@ -513,8 +506,9 @@ export default class LValParser extends NodeUtils {
* `[key: string, parenthesized: false]`.
*
* @param {NodeType} type A Node `type` string
* @param {boolean} isParenthesized
* Whether the node in question is parenthesized.
* @param {boolean} isUnparenthesizedInAssign
* Whether the node in question is unparenthesized and its parent
* is either an assignment pattern or an assignment expression.
* @param {BindingTypes} binding
* The binding operation that is being considered for this potential
* LVal.
Expand All @@ -525,8 +519,13 @@ export default class LValParser extends NodeUtils {
* A `[string, boolean]` tuple if we need to check this child and
* treat is as parenthesized.
*/
// eslint-disable-next-line no-unused-vars
isValidLVal(type: string, isParenthesized: boolean, binding: BindingTypes) {
isValidLVal(
type: string,
// eslint-disable-next-line no-unused-vars
isUnparenthesizedInAssign: boolean,
// eslint-disable-next-line no-unused-vars
binding: BindingTypes,
) {
return getOwn(
{
AssignmentPattern: "left",
Expand Down Expand Up @@ -625,7 +624,8 @@ export default class LValParser extends NodeUtils {

const validity = this.isValidLVal(
expression.type,
hasParenthesizedAncestor || expression.extra?.parenthesized,
!(hasParenthesizedAncestor || expression.extra?.parenthesized) &&
ancestor.type === "AssignmentExpression",
binding,
);

Expand Down Expand Up @@ -707,14 +707,20 @@ export default class LValParser extends NodeUtils {
this.scope.declareName(identifier.name, binding, identifier.loc.start);
}

checkToRestConversion(node: SpreadElement): void {
if (
node.argument.type !== "Identifier" &&
node.argument.type !== "MemberExpression"
) {
this.raise(Errors.InvalidRestAssignmentPattern, {
at: node.argument,
});
checkToRestConversion(node: Node, allowPattern: boolean): void {
switch (node.type) {
case "ParenthesizedExpression":
this.checkToRestConversion(node.expression, allowPattern);
break;
case "Identifier":
case "MemberExpression":
break;
case "ArrayExpression":
case "ObjectExpression":
if (allowPattern) break;
/* falls through */
default:
this.raise(Errors.InvalidRestAssignmentPattern, { at: node });
}
}

Expand Down
11 changes: 5 additions & 6 deletions packages/babel-parser/src/plugins/estree.js
Expand Up @@ -354,7 +354,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return super.isAssignable(node, isBinding);
}

toAssignable(node: N.Node, isLHS: boolean = false): N.Node {
toAssignable(node: N.Node, isLHS: boolean = false): void {
if (node != null && this.isObjectProperty(node)) {
const { key, value } = node;
if (this.isPrivateName(key)) {
Expand All @@ -364,19 +364,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
);
}
this.toAssignable(value, isLHS);
return node;
} else {
super.toAssignable(node, isLHS);
}

return super.toAssignable(node, isLHS);
}

toAssignableObjectExpressionProp(prop: N.Node, ...args) {
toAssignableObjectExpressionProp(prop: N.Node) {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(Errors.PatternHasAccessor, { at: prop.key });
} else if (prop.method) {
this.raise(Errors.PatternHasMethod, { at: prop.key });
} else {
super.toAssignableObjectExpressionProp(prop, ...args);
super.toAssignableObjectExpressionProp(...arguments);
}
}

Expand Down
17 changes: 10 additions & 7 deletions packages/babel-parser/src/plugins/flow/index.js
Expand Up @@ -2362,27 +2362,30 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}
}

toAssignable(node: N.Node, isLHS: boolean = false): N.Node {
if (node.type === "TypeCastExpression") {
return super.toAssignable(this.typeCastToParameter(node), isLHS);
} else {
return super.toAssignable(node, isLHS);
toAssignable(node: N.Node, isLHS: boolean = false): void {
if (
!isLHS &&
node.type === "AssignmentExpression" &&
node.left.type === "TypeCastExpression"
) {
node.left = this.typeCastToParameter(node.left);
}
super.toAssignable(...arguments);
}

// turn type casts that we found in function parameter head into type annotated params
toAssignableList(
exprList: N.Expression[],
trailingCommaLoc?: ?Position,
isLHS: boolean,
): $ReadOnlyArray<N.Pattern> {
): void {
for (let i = 0; i < exprList.length; i++) {
const expr = exprList[i];
if (expr?.type === "TypeCastExpression") {
exprList[i] = this.typeCastToParameter(expr);
}
}
return super.toAssignableList(exprList, trailingCommaLoc, isLHS);
super.toAssignableList(exprList, trailingCommaLoc, isLHS);
}

// this is a list of nodes, from something like a call expression, we need to filter the
Expand Down
6 changes: 3 additions & 3 deletions packages/babel-parser/src/plugins/placeholders.js
Expand Up @@ -140,16 +140,16 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return type === "Placeholder" || super.isValidLVal(type, ...rest);
}

toAssignable(node: N.Node): N.Node {
toAssignable(node: N.Node): void {
if (
node &&
node.type === "Placeholder" &&
node.expectedNode === "Expression"
) {
node.expectedNode = "Pattern";
return node;
} else {
super.toAssignable(...arguments);
}
return super.toAssignable(...arguments);
}

/* ============================================================ *
Expand Down

0 comments on commit 0e6900d

Please sign in to comment.