New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix parsing ts type casts and nested patterns in destructuring #14500
Changes from all commits
e7747b3
408db9b
35e9ee9
1de2335
bd5e86d
f4f12aa
1dd0481
66f28e8
f3402e9
bea42da
0a2d3a3
cdbd0a3
d86a2b5
8be7c27
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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": | ||
|
@@ -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( | ||
|
@@ -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); | ||
} | ||
|
@@ -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 { | ||
|
@@ -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. | ||
|
@@ -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", | ||
|
@@ -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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was needed to disallow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a new test case? for (a as b of []); It is disallowed in 7.16.2 but tsc allows it, and it seems this PR also fixes this one, too. |
||
binding, | ||
); | ||
|
||
|
@@ -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 }); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can handle
SpreadElement
intoAssignableObjectExpressionProp
andtoAssignableList
, so we don't need to trackisInObjectPattern
anymore.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! 69719e4