From 141ee01c8cd5e04a69b66db7e8d3da6da13c7499 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 13 Apr 2020 12:31:14 -0700 Subject: [PATCH] Retain imports in declaration emit if they augment an export of the importing file (#37820) * Retain imports in declaration emit if they augment an export of the importing file * (sp) * Check that a merge occurs, just because --- src/compiler/checker.ts | 24 +++++++- src/compiler/emitter.ts | 1 + src/compiler/transformers/declarations.ts | 10 ++++ src/compiler/types.ts | 1 + ...mportingModuleAugmentationRetainsImport.js | 56 +++++++++++++++++++ ...ingModuleAugmentationRetainsImport.symbols | 46 +++++++++++++++ ...rtingModuleAugmentationRetainsImport.types | 46 +++++++++++++++ ...mportingModuleAugmentationRetainsImport.ts | 20 +++++++ 8 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.js create mode 100644 tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.symbols create mode 100644 tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.types create mode 100644 tests/cases/compiler/declarationEmitForModuleImportingModuleAugmentationRetainsImport.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae1f4c4dd84..d627a55ce8b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -36118,9 +36118,31 @@ namespace ts { return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); } return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); - } + }, + isImportRequiredByAugmentation, }; + function isImportRequiredByAugmentation(node: ImportDeclaration) { + const file = getSourceFileOfNode(node); + if (!file.symbol) return false; + const importTarget = getExternalModuleFileFromDeclaration(node); + if (!importTarget) return false; + if (importTarget === file) return false; + const exports = getExportsOfModule(file.symbol); + for (const s of arrayFrom(exports.values())) { + if (s.mergeId) { + const merged = getMergedSymbol(s); + for (const d of merged.declarations) { + const declFile = getSourceFileOfNode(d); + if (declFile === importTarget) { + return true; + } + } + } + } + return false; + } + function isInHeritageClause(node: PropertyAccessEntityNameExpression) { return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 3f52f4e9ee9..d558ce785a8 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -664,6 +664,7 @@ namespace ts { getSymbolOfExternalModuleSpecifier: notImplemented, isBindingCapturedByNode: notImplemented, getDeclarationStatementsForSourceFile: notImplemented, + isImportRequiredByAugmentation: notImplemented, }; /*@internal*/ diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 743aa1b4769..70f70a98c3e 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -717,6 +717,16 @@ namespace ts { rewriteModuleSpecifier(decl, decl.moduleSpecifier) ); } + // Augmentation of export depends on import + if (resolver.isImportRequiredByAugmentation(decl)) { + return updateImportDeclaration( + decl, + /*decorators*/ undefined, + decl.modifiers, + /*importClause*/ undefined, + rewriteModuleSpecifier(decl, decl.moduleSpecifier) + ); + } // Nothing visible } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b0a0658130b..5e1b0a9fca8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4004,6 +4004,7 @@ namespace ts { getSymbolOfExternalModuleSpecifier(node: StringLiteralLike): Symbol | undefined; isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement): boolean; getDeclarationStatementsForSourceFile(node: SourceFile, flags: NodeBuilderFlags, tracker: SymbolTracker, bundled?: boolean): Statement[] | undefined; + isImportRequiredByAugmentation(decl: ImportDeclaration): boolean; } export const enum SymbolFlags { diff --git a/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.js b/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.js new file mode 100644 index 00000000000..576b8dffbde --- /dev/null +++ b/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.js @@ -0,0 +1,56 @@ +//// [tests/cases/compiler/declarationEmitForModuleImportingModuleAugmentationRetainsImport.ts] //// + +//// [child1.ts] +import { ParentThing } from './parent'; + +declare module './parent' { + interface ParentThing { + add: (a: number, b: number) => number; + } +} + +export function child1(prototype: ParentThing) { + prototype.add = (a: number, b: number) => a + b; +} + +//// [parent.ts] +import { child1 } from './child1'; // this import should still exist in some form in the output, since it augments this module + +export class ParentThing implements ParentThing {} + +child1(ParentThing.prototype); + +//// [parent.js] +"use strict"; +exports.__esModule = true; +exports.ParentThing = void 0; +var child1_1 = require("./child1"); // this import should still exist in some form in the output, since it augments this module +var ParentThing = /** @class */ (function () { + function ParentThing() { + } + return ParentThing; +}()); +exports.ParentThing = ParentThing; +child1_1.child1(ParentThing.prototype); +//// [child1.js] +"use strict"; +exports.__esModule = true; +exports.child1 = void 0; +function child1(prototype) { + prototype.add = function (a, b) { return a + b; }; +} +exports.child1 = child1; + + +//// [parent.d.ts] +import './child1'; +export declare class ParentThing implements ParentThing { +} +//// [child1.d.ts] +import { ParentThing } from './parent'; +declare module './parent' { + interface ParentThing { + add: (a: number, b: number) => number; + } +} +export declare function child1(prototype: ParentThing): void; diff --git a/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.symbols b/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.symbols new file mode 100644 index 00000000000..f7026a83b1b --- /dev/null +++ b/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.symbols @@ -0,0 +1,46 @@ +=== tests/cases/compiler/child1.ts === +import { ParentThing } from './parent'; +>ParentThing : Symbol(ParentThing, Decl(child1.ts, 0, 8)) + +declare module './parent' { +>'./parent' : Symbol("tests/cases/compiler/parent", Decl(parent.ts, 0, 0), Decl(child1.ts, 0, 39)) + + interface ParentThing { +>ParentThing : Symbol(ParentThing, Decl(parent.ts, 0, 34), Decl(child1.ts, 2, 27)) + + add: (a: number, b: number) => number; +>add : Symbol(ParentThing.add, Decl(child1.ts, 3, 27)) +>a : Symbol(a, Decl(child1.ts, 4, 14)) +>b : Symbol(b, Decl(child1.ts, 4, 24)) + } +} + +export function child1(prototype: ParentThing) { +>child1 : Symbol(child1, Decl(child1.ts, 6, 1)) +>prototype : Symbol(prototype, Decl(child1.ts, 8, 23)) +>ParentThing : Symbol(ParentThing, Decl(child1.ts, 0, 8)) + + prototype.add = (a: number, b: number) => a + b; +>prototype.add : Symbol(ParentThing.add, Decl(child1.ts, 3, 27)) +>prototype : Symbol(prototype, Decl(child1.ts, 8, 23)) +>add : Symbol(ParentThing.add, Decl(child1.ts, 3, 27)) +>a : Symbol(a, Decl(child1.ts, 9, 21)) +>b : Symbol(b, Decl(child1.ts, 9, 31)) +>a : Symbol(a, Decl(child1.ts, 9, 21)) +>b : Symbol(b, Decl(child1.ts, 9, 31)) +} + +=== tests/cases/compiler/parent.ts === +import { child1 } from './child1'; // this import should still exist in some form in the output, since it augments this module +>child1 : Symbol(child1, Decl(parent.ts, 0, 8)) + +export class ParentThing implements ParentThing {} +>ParentThing : Symbol(ParentThing, Decl(parent.ts, 0, 34), Decl(child1.ts, 2, 27)) +>ParentThing : Symbol(ParentThing, Decl(parent.ts, 0, 34), Decl(child1.ts, 2, 27)) + +child1(ParentThing.prototype); +>child1 : Symbol(child1, Decl(parent.ts, 0, 8)) +>ParentThing.prototype : Symbol(ParentThing.prototype) +>ParentThing : Symbol(ParentThing, Decl(parent.ts, 0, 34), Decl(child1.ts, 2, 27)) +>prototype : Symbol(ParentThing.prototype) + diff --git a/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.types b/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.types new file mode 100644 index 00000000000..bcb3f3a0666 --- /dev/null +++ b/tests/baselines/reference/declarationEmitForModuleImportingModuleAugmentationRetainsImport.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/child1.ts === +import { ParentThing } from './parent'; +>ParentThing : typeof ParentThing + +declare module './parent' { +>'./parent' : typeof import("tests/cases/compiler/parent") + + interface ParentThing { + add: (a: number, b: number) => number; +>add : (a: number, b: number) => number +>a : number +>b : number + } +} + +export function child1(prototype: ParentThing) { +>child1 : (prototype: ParentThing) => void +>prototype : ParentThing + + prototype.add = (a: number, b: number) => a + b; +>prototype.add = (a: number, b: number) => a + b : (a: number, b: number) => number +>prototype.add : (a: number, b: number) => number +>prototype : ParentThing +>add : (a: number, b: number) => number +>(a: number, b: number) => a + b : (a: number, b: number) => number +>a : number +>b : number +>a + b : number +>a : number +>b : number +} + +=== tests/cases/compiler/parent.ts === +import { child1 } from './child1'; // this import should still exist in some form in the output, since it augments this module +>child1 : (prototype: ParentThing) => void + +export class ParentThing implements ParentThing {} +>ParentThing : ParentThing + +child1(ParentThing.prototype); +>child1(ParentThing.prototype) : void +>child1 : (prototype: ParentThing) => void +>ParentThing.prototype : ParentThing +>ParentThing : typeof ParentThing +>prototype : ParentThing + diff --git a/tests/cases/compiler/declarationEmitForModuleImportingModuleAugmentationRetainsImport.ts b/tests/cases/compiler/declarationEmitForModuleImportingModuleAugmentationRetainsImport.ts new file mode 100644 index 00000000000..5d6268864fe --- /dev/null +++ b/tests/cases/compiler/declarationEmitForModuleImportingModuleAugmentationRetainsImport.ts @@ -0,0 +1,20 @@ +// @declaration: true +// @filename: child1.ts +import { ParentThing } from './parent'; + +declare module './parent' { + interface ParentThing { + add: (a: number, b: number) => number; + } +} + +export function child1(prototype: ParentThing) { + prototype.add = (a: number, b: number) => a + b; +} + +// @filename: parent.ts +import { child1 } from './child1'; // this import should still exist in some form in the output, since it augments this module + +export class ParentThing implements ParentThing {} + +child1(ParentThing.prototype); \ No newline at end of file