diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1673c6c0327..739db56fbcd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2683,7 +2683,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (source.exports) { if (!target.exports) target.exports = createSymbolTable(); - mergeSymbolTable(target.exports, source.exports, unidirectional); + mergeSymbolTable(target.exports, source.exports, unidirectional, target); } if (!unidirectional) { recordMergedSymbol(target, source); @@ -2772,10 +2772,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return combined; } - function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { + function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false, mergedParent?: Symbol) { source.forEach((sourceSymbol, id) => { const targetSymbol = target.get(id); - target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : getMergedSymbol(sourceSymbol)); + const merged = targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : getMergedSymbol(sourceSymbol); + if (mergedParent && targetSymbol) { + // If a merge was performed on the target symbol, set its parent to the merged parent that initiated the merge + // of its exports. Otherwise, `merged` came only from `sourceSymbol` and can keep its parent: + // + // // a.ts + // export interface A { x: number; } + // + // // b.ts + // declare module "./a" { + // interface A { y: number; } + // interface B {} + // } + // + // When merging the module augmentation into a.ts, the symbol for `A` will itself be merged, so its parent + // should be the merged module symbol. But the symbol for `B` has only one declaration, so its parent should + // be the module augmentation symbol, which contains its only declaration. + merged.parent = mergedParent; + } + target.set(id, merged); }); } diff --git a/tests/cases/fourslash/autoImportCompletionExportListAugmentation1.ts b/tests/cases/fourslash/autoImportCompletionExportListAugmentation1.ts new file mode 100644 index 00000000000..8556ac9c40f --- /dev/null +++ b/tests/cases/fourslash/autoImportCompletionExportListAugmentation1.ts @@ -0,0 +1,58 @@ +/// +// @module: nodenext + +// @Filename: /node_modules/@sapphire/pieces/index.d.ts +//// interface Container { +//// stores: unknown; +//// } +//// +//// declare class Piece { +//// container: Container; +//// } +//// +//// export { Piece, type Container }; + +// @FileName: /augmentation.ts +//// declare module "@sapphire/pieces" { +//// interface Container { +//// client: unknown; +//// } +//// export { Container }; +//// } + +// @Filename: /index.ts +//// import { Piece } from "@sapphire/pieces"; +//// class FullPiece extends Piece { +//// /*1*/ +//// } + +const preferences = { + includeCompletionsWithClassMemberSnippets: true, + includeCompletionsWithInsertText: true, +}; + +verify.completions({ + marker: "1", + includes: [ + { + name: "container", + insertText: "container: Container;", + filterText: "container", + hasAction: true, + source: "ClassMemberSnippet/", + }, + ], + preferences, + isNewIdentifierLocation: true, +}); + +verify.applyCodeActionFromCompletion("1", { + name: "container", + source: "ClassMemberSnippet/", + description: `Includes imports of types referenced by 'container'`, + newFileContent: `import { Container, Piece } from "@sapphire/pieces"; +class FullPiece extends Piece { + +}`, + preferences, +}); diff --git a/tests/cases/fourslash/autoImportCompletionExportListAugmentation2.ts b/tests/cases/fourslash/autoImportCompletionExportListAugmentation2.ts new file mode 100644 index 00000000000..bf984311cfb --- /dev/null +++ b/tests/cases/fourslash/autoImportCompletionExportListAugmentation2.ts @@ -0,0 +1,68 @@ +/// +// @module: nodenext + +// @Filename: /node_modules/@sapphire/pieces/index.d.ts +//// interface Container { +//// stores: unknown; +//// } +//// +//// declare class Piece { +//// get container(): Container; +//// } +//// +//// declare class AliasPiece extends Piece {} +//// +//// export { AliasPiece, type Container }; + +// @Filename: /node_modules/@sapphire/framework/index.d.ts +//// import { AliasPiece } from "@sapphire/pieces"; +//// +//// declare class Command extends AliasPiece {} +//// +//// declare module "@sapphire/pieces" { +//// interface Container { +//// client: unknown; +//// } +//// } +//// +//// export { Command }; + +// @Filename: /index.ts +//// import "@sapphire/pieces"; +//// import { Command } from "@sapphire/framework"; +//// class PingCommand extends Command { +//// /*1*/ +//// } + +const preferences = { + includeCompletionsWithClassMemberSnippets: true, + includeCompletionsWithInsertText: true, +}; + +verify.completions({ + marker: "1", + includes: [ + { + name: "container", + insertText: "get container(): Container {\n}", + filterText: "container", + hasAction: true, + source: "ClassMemberSnippet/", + }, + ], + preferences, + isNewIdentifierLocation: true, +}); + +verify.applyCodeActionFromCompletion("1", { + name: "container", + source: "ClassMemberSnippet/", + description: `Includes imports of types referenced by 'container'`, + newFileContent: `import "@sapphire/pieces"; +import { Command } from "@sapphire/framework"; +import { Container } from "@sapphire/pieces"; +class PingCommand extends Command { + +}`, + preferences, +}); \ No newline at end of file diff --git a/tests/cases/fourslash/autoImportCompletionExportListAugmentation3.ts b/tests/cases/fourslash/autoImportCompletionExportListAugmentation3.ts new file mode 100644 index 00000000000..7331ec3cedc --- /dev/null +++ b/tests/cases/fourslash/autoImportCompletionExportListAugmentation3.ts @@ -0,0 +1,57 @@ +/// +// @module: nodenext + +// @Filename: /node_modules/@sapphire/pieces/index.d.ts +//// export interface Container { +//// stores: unknown; +//// } +//// +//// declare class Piece { +//// container: Container; +//// } +//// +//// export { Piece }; + +// @FileName: /augmentation.ts +//// declare module "@sapphire/pieces" { +//// interface Container { +//// client: unknown; +//// } +//// } + +// @Filename: /index.ts +//// import { Piece } from "@sapphire/pieces"; +//// class FullPiece extends Piece { +//// /*1*/ +//// } + +const preferences = { + includeCompletionsWithClassMemberSnippets: true, + includeCompletionsWithInsertText: true, +}; + +verify.completions({ + marker: "1", + includes: [ + { + name: "container", + insertText: "container: Container;", + filterText: "container", + hasAction: true, + source: "ClassMemberSnippet/", + }, + ], + preferences, + isNewIdentifierLocation: true, +}); + +verify.applyCodeActionFromCompletion("1", { + name: "container", + source: "ClassMemberSnippet/", + description: `Includes imports of types referenced by 'container'`, + newFileContent: `import { Container, Piece } from "@sapphire/pieces"; +class FullPiece extends Piece { + +}`, + preferences, +}); \ No newline at end of file diff --git a/tests/cases/fourslash/autoImportCompletionExportListAugmentation4.ts b/tests/cases/fourslash/autoImportCompletionExportListAugmentation4.ts new file mode 100644 index 00000000000..58ded632a70 --- /dev/null +++ b/tests/cases/fourslash/autoImportCompletionExportListAugmentation4.ts @@ -0,0 +1,67 @@ +/// + +// @module: nodenext + +// @Filename: /node_modules/@sapphire/pieces/index.d.ts +//// interface Container { +//// stores: unknown; +//// } +//// +//// declare class Piece { +//// get container(): Container; +//// } +//// +//// export { Piece as Alias, type Container }; + +// @Filename: /node_modules/@sapphire/framework/index.d.ts +//// import { Alias } from "@sapphire/pieces"; +//// +//// declare class Command extends Alias {} +//// +//// declare module "@sapphire/pieces" { +//// interface Container { +//// client: unknown; +//// } +//// } +//// +//// export { Command as CommandAlias }; + +// @Filename: /index.ts +//// import "@sapphire/pieces"; +//// import { CommandAlias } from "@sapphire/framework"; +//// class PingCommand extends CommandAlias { +//// /*1*/ +//// } + +const preferences = { + includeCompletionsWithClassMemberSnippets: true, + includeCompletionsWithInsertText: true, +}; + +verify.completions({ + marker: "1", + includes: [ + { + name: "container", + insertText: "get container(): Container {\n}", + filterText: "container", + hasAction: true, + source: "ClassMemberSnippet/", + }, + ], + preferences, + isNewIdentifierLocation: true, +}); + +verify.applyCodeActionFromCompletion("1", { + name: "container", + source: "ClassMemberSnippet/", + description: `Includes imports of types referenced by 'container'`, + newFileContent: `import "@sapphire/pieces"; +import { CommandAlias } from "@sapphire/framework"; +import { Container } from "@sapphire/pieces"; +class PingCommand extends CommandAlias { + +}`, + preferences, +}); \ No newline at end of file