From 9c40d276abef87db4f7bbaccc4e18bec2b626341 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 2 May 2018 16:20:47 -0700 Subject: [PATCH] Downlevel destructuring in module transformer if destructured variable has multiple names (#23832) * Downlevel destructuring in module transformer if destructured variable has multiple names * Alter indentation --- src/compiler/transformers/module/module.ts | 109 +++++++++++++++--- ...destructuringAssignmentWithExportedName.js | 53 +++++++++ ...ucturingAssignmentWithExportedName.symbols | 54 +++++++++ ...tructuringAssignmentWithExportedName.types | 97 ++++++++++++++++ ...destructuringAssignmentWithExportedName.ts | 27 +++++ 5 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 tests/baselines/reference/destructuringAssignmentWithExportedName.js create mode 100644 tests/baselines/reference/destructuringAssignmentWithExportedName.symbols create mode 100644 tests/baselines/reference/destructuringAssignmentWithExportedName.types create mode 100644 tests/cases/compiler/destructuringAssignmentWithExportedName.ts diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index f9fb47f6a27..d87712d451d 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -451,7 +451,7 @@ namespace ts { */ function addExportEqualsIfNeeded(statements: Statement[], emitAsReturn: boolean) { if (currentModuleInfo.exportEquals) { - const expressionResult = visitNode(currentModuleInfo.exportEquals.expression, importCallExpressionVisitor); + const expressionResult = visitNode(currentModuleInfo.exportEquals.expression, moduleExpressionElementVisitor); if (expressionResult) { if (emitAsReturn) { const statement = createReturn(expressionResult); @@ -517,27 +517,82 @@ namespace ts { return visitEndOfDeclarationMarker(node); default: - return visitEachChild(node, importCallExpressionVisitor, context); + return visitEachChild(node, moduleExpressionElementVisitor, context); } } - function importCallExpressionVisitor(node: Expression): VisitResult { - // This visitor does not need to descend into the tree if there is no dynamic import, + function moduleExpressionElementVisitor(node: Expression): VisitResult { + // This visitor does not need to descend into the tree if there is no dynamic import or destructuring assignment, // as export/import statements are only transformed at the top level of a file. - if (!(node.transformFlags & TransformFlags.ContainsDynamicImport)) { + if (!(node.transformFlags & TransformFlags.ContainsDynamicImport) && !(node.transformFlags & TransformFlags.ContainsDestructuringAssignment)) { return node; } if (isImportCall(node)) { return visitImportCallExpression(node); } + else if (node.transformFlags & TransformFlags.DestructuringAssignment && isBinaryExpression(node)) { + return visitDestructuringAssignment(node as DestructuringAssignment); + } else { - return visitEachChild(node, importCallExpressionVisitor, context); + return visitEachChild(node, moduleExpressionElementVisitor, context); } } + function destructuringNeedsFlattening(node: Expression): boolean { + if (isObjectLiteralExpression(node)) { + for (const elem of node.properties) { + switch (elem.kind) { + case SyntaxKind.PropertyAssignment: + if (destructuringNeedsFlattening(elem.initializer)) { + return true; + } + break; + case SyntaxKind.ShorthandPropertyAssignment: + if (destructuringNeedsFlattening(elem.name)) { + return true; + } + break; + case SyntaxKind.SpreadAssignment: + if (destructuringNeedsFlattening(elem.expression)) { + return true; + } + break; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return false; + default: Debug.assertNever(elem, "Unhandled object member kind"); + } + } + } + else if (isArrayLiteralExpression(node)) { + for (const elem of node.elements) { + if (isSpreadElement(elem)) { + if (destructuringNeedsFlattening(elem.expression)) { + return true; + } + } + else if (destructuringNeedsFlattening(elem)) { + return true; + } + } + } + else if (isIdentifier(node)) { + return length(getExports(node)) > (isExportName(node) ? 1 : 0); + } + return false; + } + + function visitDestructuringAssignment(node: DestructuringAssignment): Expression { + if (destructuringNeedsFlattening(node.left)) { + return flattenDestructuringAssignment(node, moduleExpressionElementVisitor, context, FlattenLevel.All, /*needsValue*/ false, createAllExportExpressions); + } + return visitEachChild(node, moduleExpressionElementVisitor, context); + } + function visitImportCallExpression(node: ImportCall): Expression { - const argument = visitNode(firstOrUndefined(node.arguments), importCallExpressionVisitor); + const argument = visitNode(firstOrUndefined(node.arguments), moduleExpressionElementVisitor); const containsLexicalThis = !!(node.transformFlags & TransformFlags.ContainsLexicalThis); switch (compilerOptions.module) { case ModuleKind.AMD: @@ -959,10 +1014,10 @@ namespace ts { if (original && hasAssociatedEndOfDeclarationMarker(original)) { // Defer exports until we encounter an EndOfDeclarationMarker node const id = getOriginalNodeId(node); - deferredExports[id] = appendExportStatement(deferredExports[id], createIdentifier("default"), visitNode(node.expression, importCallExpressionVisitor), /*location*/ node, /*allowComments*/ true); + deferredExports[id] = appendExportStatement(deferredExports[id], createIdentifier("default"), visitNode(node.expression, moduleExpressionElementVisitor), /*location*/ node, /*allowComments*/ true); } else { - statements = appendExportStatement(statements, createIdentifier("default"), visitNode(node.expression, importCallExpressionVisitor), /*location*/ node, /*allowComments*/ true); + statements = appendExportStatement(statements, createIdentifier("default"), visitNode(node.expression, moduleExpressionElementVisitor), /*location*/ node, /*allowComments*/ true); } return singleOrMany(statements); @@ -985,9 +1040,9 @@ namespace ts { node.asteriskToken, getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), /*typeParameters*/ undefined, - visitNodes(node.parameters, importCallExpressionVisitor), + visitNodes(node.parameters, moduleExpressionElementVisitor), /*type*/ undefined, - visitEachChild(node.body, importCallExpressionVisitor, context) + visitEachChild(node.body, moduleExpressionElementVisitor, context) ), /*location*/ node ), @@ -996,7 +1051,7 @@ namespace ts { ); } else { - statements = append(statements, visitEachChild(node, importCallExpressionVisitor, context)); + statements = append(statements, visitEachChild(node, moduleExpressionElementVisitor, context)); } if (hasAssociatedEndOfDeclarationMarker(node)) { @@ -1027,8 +1082,8 @@ namespace ts { visitNodes(node.modifiers, modifierVisitor, isModifier), getDeclarationName(node, /*allowComments*/ true, /*allowSourceMaps*/ true), /*typeParameters*/ undefined, - visitNodes(node.heritageClauses, importCallExpressionVisitor), - visitNodes(node.members, importCallExpressionVisitor) + visitNodes(node.heritageClauses, moduleExpressionElementVisitor), + visitNodes(node.members, moduleExpressionElementVisitor) ), node ), @@ -1037,7 +1092,7 @@ namespace ts { ); } else { - statements = append(statements, visitEachChild(node, importCallExpressionVisitor, context)); + statements = append(statements, visitEachChild(node, moduleExpressionElementVisitor, context)); } if (hasAssociatedEndOfDeclarationMarker(node)) { @@ -1089,7 +1144,7 @@ namespace ts { } } else { - statements = append(statements, visitEachChild(node, importCallExpressionVisitor, context)); + statements = append(statements, visitEachChild(node, moduleExpressionElementVisitor, context)); } if (hasAssociatedEndOfDeclarationMarker(node)) { @@ -1104,6 +1159,22 @@ namespace ts { return singleOrMany(statements); } + function createAllExportExpressions(name: Identifier, value: Expression, location?: TextRange) { + const exportedNames = getExports(name); + if (exportedNames) { + // For each additional export of the declaration, apply an export assignment. + let expression: Expression = isExportName(name) ? value : createAssignment(name, value); + for (const exportName of exportedNames) { + // Mark the node to prevent triggering substitution. + setEmitFlags(expression, EmitFlags.NoSubstitution); + expression = createExportExpression(exportName, expression, /*location*/ location); + } + + return expression; + } + return createAssignment(name, value); + } + /** * Transforms an exported variable with an initializer into an expression. * @@ -1112,12 +1183,12 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration): Expression { if (isBindingPattern(node.name)) { return flattenDestructuringAssignment( - visitNode(node, importCallExpressionVisitor), + visitNode(node, moduleExpressionElementVisitor), /*visitor*/ undefined, context, FlattenLevel.All, /*needsValue*/ false, - createExportExpression + createAllExportExpressions ); } else { @@ -1129,7 +1200,7 @@ namespace ts { ), /*location*/ node.name ), - visitNode(node.initializer, importCallExpressionVisitor) + visitNode(node.initializer, moduleExpressionElementVisitor) ); } } diff --git a/tests/baselines/reference/destructuringAssignmentWithExportedName.js b/tests/baselines/reference/destructuringAssignmentWithExportedName.js new file mode 100644 index 00000000000..a23bec7b5d8 --- /dev/null +++ b/tests/baselines/reference/destructuringAssignmentWithExportedName.js @@ -0,0 +1,53 @@ +//// [destructuringAssignmentWithExportedName.ts] +export let exportedFoo: any; +let nonexportedFoo: any; + +// sanity checks +exportedFoo = null; +nonexportedFoo = null; + +if (null as any) { + ({ exportedFoo, nonexportedFoo } = null as any); +} +else if (null as any) { + ({ foo: exportedFoo, bar: nonexportedFoo } = null as any); +} +else if (null as any) { + ({ foo: { bar: exportedFoo, baz: nonexportedFoo } } = null as any); +} +else if (null as any) { + ([exportedFoo, nonexportedFoo] = null as any); +} +else { + ([[exportedFoo, nonexportedFoo]] = null as any); +} + +export { nonexportedFoo }; +export { exportedFoo as foo, nonexportedFoo as nfoo }; + +//// [destructuringAssignmentWithExportedName.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.foo = exports.exportedFoo; +let nonexportedFoo; +exports.nonexportedFoo = nonexportedFoo; +exports.nfoo = nonexportedFoo; +// sanity checks +exports.foo = exports.exportedFoo = null; +exports.nfoo = exports.nonexportedFoo = nonexportedFoo = null; +if (null) { + (_a = null, exports.foo = exports.exportedFoo = _a.exportedFoo, exports.nfoo = exports.nonexportedFoo = nonexportedFoo = _a.nonexportedFoo); +} +else if (null) { + (_b = null, exports.foo = exports.exportedFoo = _b.foo, exports.nfoo = exports.nonexportedFoo = nonexportedFoo = _b.bar); +} +else if (null) { + (_c = null.foo, exports.foo = exports.exportedFoo = _c.bar, exports.nfoo = exports.nonexportedFoo = nonexportedFoo = _c.baz); +} +else if (null) { + (_d = null, exports.foo = exports.exportedFoo = _d[0], exports.nfoo = exports.nonexportedFoo = nonexportedFoo = _d[1]); +} +else { + (_e = null[0], exports.foo = exports.exportedFoo = _e[0], exports.nfoo = exports.nonexportedFoo = nonexportedFoo = _e[1]); +} +var _a, _b, _c, _d, _e; diff --git a/tests/baselines/reference/destructuringAssignmentWithExportedName.symbols b/tests/baselines/reference/destructuringAssignmentWithExportedName.symbols new file mode 100644 index 00000000000..28b2392c533 --- /dev/null +++ b/tests/baselines/reference/destructuringAssignmentWithExportedName.symbols @@ -0,0 +1,54 @@ +=== tests/cases/compiler/destructuringAssignmentWithExportedName.ts === +export let exportedFoo: any; +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 0, 10)) + +let nonexportedFoo: any; +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 1, 3)) + +// sanity checks +exportedFoo = null; +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 0, 10)) + +nonexportedFoo = null; +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 1, 3)) + +if (null as any) { + ({ exportedFoo, nonexportedFoo } = null as any); +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 8, 6)) +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 8, 19)) +} +else if (null as any) { + ({ foo: exportedFoo, bar: nonexportedFoo } = null as any); +>foo : Symbol(foo, Decl(destructuringAssignmentWithExportedName.ts, 11, 3)) +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 0, 10)) +>bar : Symbol(bar, Decl(destructuringAssignmentWithExportedName.ts, 11, 21)) +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 1, 3)) +} +else if (null as any) { + ({ foo: { bar: exportedFoo, baz: nonexportedFoo } } = null as any); +>foo : Symbol(foo, Decl(destructuringAssignmentWithExportedName.ts, 14, 3)) +>bar : Symbol(bar, Decl(destructuringAssignmentWithExportedName.ts, 14, 10)) +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 0, 10)) +>baz : Symbol(baz, Decl(destructuringAssignmentWithExportedName.ts, 14, 28)) +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 1, 3)) +} +else if (null as any) { + ([exportedFoo, nonexportedFoo] = null as any); +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 0, 10)) +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 1, 3)) +} +else { + ([[exportedFoo, nonexportedFoo]] = null as any); +>exportedFoo : Symbol(exportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 0, 10)) +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 1, 3)) +} + +export { nonexportedFoo }; +>nonexportedFoo : Symbol(nonexportedFoo, Decl(destructuringAssignmentWithExportedName.ts, 23, 8)) + +export { exportedFoo as foo, nonexportedFoo as nfoo }; +>exportedFoo : Symbol(foo, Decl(destructuringAssignmentWithExportedName.ts, 24, 8)) +>foo : Symbol(foo, Decl(destructuringAssignmentWithExportedName.ts, 24, 8)) +>nonexportedFoo : Symbol(nfoo, Decl(destructuringAssignmentWithExportedName.ts, 24, 28)) +>nfoo : Symbol(nfoo, Decl(destructuringAssignmentWithExportedName.ts, 24, 28)) + diff --git a/tests/baselines/reference/destructuringAssignmentWithExportedName.types b/tests/baselines/reference/destructuringAssignmentWithExportedName.types new file mode 100644 index 00000000000..fc5264ae59f --- /dev/null +++ b/tests/baselines/reference/destructuringAssignmentWithExportedName.types @@ -0,0 +1,97 @@ +=== tests/cases/compiler/destructuringAssignmentWithExportedName.ts === +export let exportedFoo: any; +>exportedFoo : any + +let nonexportedFoo: any; +>nonexportedFoo : any + +// sanity checks +exportedFoo = null; +>exportedFoo = null : null +>exportedFoo : any +>null : null + +nonexportedFoo = null; +>nonexportedFoo = null : null +>nonexportedFoo : any +>null : null + +if (null as any) { +>null as any : any +>null : null + + ({ exportedFoo, nonexportedFoo } = null as any); +>({ exportedFoo, nonexportedFoo } = null as any) : any +>{ exportedFoo, nonexportedFoo } = null as any : any +>{ exportedFoo, nonexportedFoo } : { exportedFoo: any; nonexportedFoo: any; } +>exportedFoo : any +>nonexportedFoo : any +>null as any : any +>null : null +} +else if (null as any) { +>null as any : any +>null : null + + ({ foo: exportedFoo, bar: nonexportedFoo } = null as any); +>({ foo: exportedFoo, bar: nonexportedFoo } = null as any) : any +>{ foo: exportedFoo, bar: nonexportedFoo } = null as any : any +>{ foo: exportedFoo, bar: nonexportedFoo } : { foo: any; bar: any; } +>foo : any +>exportedFoo : any +>bar : any +>nonexportedFoo : any +>null as any : any +>null : null +} +else if (null as any) { +>null as any : any +>null : null + + ({ foo: { bar: exportedFoo, baz: nonexportedFoo } } = null as any); +>({ foo: { bar: exportedFoo, baz: nonexportedFoo } } = null as any) : any +>{ foo: { bar: exportedFoo, baz: nonexportedFoo } } = null as any : any +>{ foo: { bar: exportedFoo, baz: nonexportedFoo } } : { foo: { bar: any; baz: any; }; } +>foo : { bar: any; baz: any; } +>{ bar: exportedFoo, baz: nonexportedFoo } : { bar: any; baz: any; } +>bar : any +>exportedFoo : any +>baz : any +>nonexportedFoo : any +>null as any : any +>null : null +} +else if (null as any) { +>null as any : any +>null : null + + ([exportedFoo, nonexportedFoo] = null as any); +>([exportedFoo, nonexportedFoo] = null as any) : any +>[exportedFoo, nonexportedFoo] = null as any : any +>[exportedFoo, nonexportedFoo] : [any, any] +>exportedFoo : any +>nonexportedFoo : any +>null as any : any +>null : null +} +else { + ([[exportedFoo, nonexportedFoo]] = null as any); +>([[exportedFoo, nonexportedFoo]] = null as any) : any +>[[exportedFoo, nonexportedFoo]] = null as any : any +>[[exportedFoo, nonexportedFoo]] : [[any, any]] +>[exportedFoo, nonexportedFoo] : [any, any] +>exportedFoo : any +>nonexportedFoo : any +>null as any : any +>null : null +} + +export { nonexportedFoo }; +>nonexportedFoo : any + +export { exportedFoo as foo, nonexportedFoo as nfoo }; +>exportedFoo : any +>foo : any +>nonexportedFoo : any +>nfoo : any + diff --git a/tests/cases/compiler/destructuringAssignmentWithExportedName.ts b/tests/cases/compiler/destructuringAssignmentWithExportedName.ts new file mode 100644 index 00000000000..d2d2132d4c4 --- /dev/null +++ b/tests/cases/compiler/destructuringAssignmentWithExportedName.ts @@ -0,0 +1,27 @@ +// @target: es2015 +// @module: commonjs +export let exportedFoo: any; +let nonexportedFoo: any; + +// sanity checks +exportedFoo = null; +nonexportedFoo = null; + +if (null as any) { + ({ exportedFoo, nonexportedFoo } = null as any); +} +else if (null as any) { + ({ foo: exportedFoo, bar: nonexportedFoo } = null as any); +} +else if (null as any) { + ({ foo: { bar: exportedFoo, baz: nonexportedFoo } } = null as any); +} +else if (null as any) { + ([exportedFoo, nonexportedFoo] = null as any); +} +else { + ([[exportedFoo, nonexportedFoo]] = null as any); +} + +export { nonexportedFoo }; +export { exportedFoo as foo, nonexportedFoo as nfoo }; \ No newline at end of file