Fix emit for nested object rest in assignment pattern (#52922)

This commit is contained in:
Ron Buckton
2023-02-22 20:41:09 -05:00
committed by GitHub
parent 718e63b9c9
commit 8becdf2b40
8 changed files with 112 additions and 26 deletions

View File

@@ -58,6 +58,7 @@ import {
ConstructorDeclaration,
ConstructorTypeNode,
ConstructSignatureDeclaration,
containsObjectRestOrSpread,
ContinueStatement,
createBaseNodeFactory,
createNodeConverters,
@@ -114,7 +115,6 @@ import {
getAllUnscopedEmitHelpers,
getBuildInfo,
getCommentRange,
getElementsOfBindingOrAssignmentPattern,
getEmitFlags,
getIdentifierTypeArguments,
getJSDocTypeAliasName,
@@ -124,7 +124,6 @@ import {
getSourceMapRange,
getSyntheticLeadingComments,
getSyntheticTrailingComments,
getTargetOfBindingOrAssignmentElement,
getTextOfIdentifierOrLiteral,
hasInvalidEscape,
HasModifiers,
@@ -150,7 +149,6 @@ import {
isArray,
isArrayLiteralExpression,
isArrowFunction,
isAssignmentPattern,
isBinaryExpression,
isCallChain,
isClassDeclaration,
@@ -3326,24 +3324,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}
function propagateAssignmentPatternFlags(node: AssignmentPattern): TransformFlags {
if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return TransformFlags.ContainsObjectRestOrSpread;
if (node.transformFlags & TransformFlags.ContainsES2018) {
// check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c'
// will not be correctly interpreted by the ES2018 transformer
for (const element of getElementsOfBindingOrAssignmentPattern(node)) {
const target = getTargetOfBindingOrAssignmentElement(element);
if (target && isAssignmentPattern(target)) {
if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
return TransformFlags.ContainsObjectRestOrSpread;
}
if (target.transformFlags & TransformFlags.ContainsES2018) {
const flags = propagateAssignmentPatternFlags(target);
if (flags) return flags;
}
}
}
}
return TransformFlags.None;
return containsObjectRestOrSpread(node) ? TransformFlags.ContainsObjectRestOrSpread : TransformFlags.None;
}
// @api

View File

@@ -7,6 +7,7 @@ import {
AssertionLevel,
AssignmentExpression,
AssignmentOperatorOrHigher,
AssignmentPattern,
BinaryExpression,
BinaryOperator,
BinaryOperatorToken,
@@ -76,6 +77,7 @@ import {
InternalEmitFlags,
isAssignmentExpression,
isAssignmentOperator,
isAssignmentPattern,
isBlock,
isCommaListExpression,
isComputedPropertyName,
@@ -174,6 +176,7 @@ import {
TextRange,
ThisTypeNode,
Token,
TransformFlags,
TypeNode,
} from "../_namespaces/ts";
@@ -1742,3 +1745,31 @@ export function flattenCommaList(node: Expression) {
flattenCommaListWorker(node, expressions);
return expressions;
}
/**
* Walk an AssignmentPattern to determine if it contains object rest (`...`) syntax. We cannot rely on
* propagation of `TransformFlags.ContainsObjectRestOrSpread` since it isn't propagated by default in
* ObjectLiteralExpression and ArrayLiteralExpression since we do not know whether they belong to an
* AssignmentPattern at the time the nodes are parsed.
*
* @internal
*/
export function containsObjectRestOrSpread(node: AssignmentPattern): boolean {
if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) return true;
if (node.transformFlags & TransformFlags.ContainsES2018) {
// check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c'
// will not be correctly interpreted by the ES2018 transformer
for (const element of getElementsOfBindingOrAssignmentPattern(node)) {
const target = getTargetOfBindingOrAssignmentElement(element);
if (target && isAssignmentPattern(target)) {
if (target.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
return true;
}
if (target.transformFlags & TransformFlags.ContainsES2018) {
if (containsObjectRestOrSpread(target)) return true;
}
}
}
}
return false;
}

View File

@@ -19,6 +19,7 @@ import {
concatenate,
ConciseBody,
ConstructorDeclaration,
containsObjectRestOrSpread,
createForOfBindingStatement,
createSuperAccessVariableStatement,
Debug,
@@ -577,7 +578,7 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
* expression of an `ExpressionStatement`).
*/
function visitBinaryExpression(node: BinaryExpression, expressionResultIsUnused: boolean): Expression {
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
if (isDestructuringAssignment(node) && containsObjectRestOrSpread(node.left)) {
return flattenDestructuringAssignment(
node,
visitor,
@@ -703,7 +704,8 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
*/
function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement | undefined): VisitResult<Statement> {
const ancestorFacts = enterSubtree(HierarchyFacts.IterationStatementExcludes, HierarchyFacts.IterationStatementIncludes);
if (node.initializer.transformFlags & TransformFlags.ContainsObjectRestOrSpread) {
if (node.initializer.transformFlags & TransformFlags.ContainsObjectRestOrSpread ||
isAssignmentPattern(node.initializer) && containsObjectRestOrSpread(node.initializer)) {
node = transformForOfStatementWithObjectRest(node);
}
const result = node.awaitModifier ?