Auto-imports: fix some exports being incorrectly stored as re-exports of others due to key conflict (#45792)

* Ensure symbol key unique when target is a local symbol exported elsewhere

* Add test

* Support targets without declarations

* Best key yet

* A-ha moment

* Clean up types

* Update API

* Update unit test
This commit is contained in:
Andrew Branch 2021-10-08 15:20:12 -07:00 committed by GitHub
parent f6c0231f08
commit 64b8172f06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 96 deletions

View file

@ -74,13 +74,9 @@ namespace ts.Completions {
interface SymbolOriginInfo { interface SymbolOriginInfo {
kind: SymbolOriginInfoKind; kind: SymbolOriginInfoKind;
symbolName?: string;
moduleSymbol?: Symbol;
isDefaultExport?: boolean; isDefaultExport?: boolean;
isFromPackageJson?: boolean; isFromPackageJson?: boolean;
exportName?: string;
fileName?: string; fileName?: string;
moduleSpecifier?: string;
} }
interface SymbolOriginInfoExport extends SymbolOriginInfo { interface SymbolOriginInfoExport extends SymbolOriginInfo {
@ -88,9 +84,13 @@ namespace ts.Completions {
moduleSymbol: Symbol; moduleSymbol: Symbol;
isDefaultExport: boolean; isDefaultExport: boolean;
exportName: string; exportName: string;
exportMapKey: string;
} }
interface SymbolOriginInfoResolvedExport extends SymbolOriginInfoExport { interface SymbolOriginInfoResolvedExport extends SymbolOriginInfo {
symbolName: string;
moduleSymbol: Symbol;
exportName: string;
moduleSpecifier: string; moduleSpecifier: string;
} }
@ -294,6 +294,10 @@ namespace ts.Completions {
} }
} }
function completionEntryDataIsResolved(data: CompletionEntryDataAutoImport | undefined): data is CompletionEntryDataResolved {
return !!data?.moduleSpecifier;
}
function continuePreviousIncompleteResponse( function continuePreviousIncompleteResponse(
cache: IncompleteCompletionsCache, cache: IncompleteCompletionsCache,
file: SourceFile, file: SourceFile,
@ -308,9 +312,6 @@ namespace ts.Completions {
const lowerCaseTokenText = location.text.toLowerCase(); const lowerCaseTokenText = location.text.toLowerCase();
const exportMap = getExportInfoMap(file, host, program, cancellationToken); const exportMap = getExportInfoMap(file, host, program, cancellationToken);
const checker = program.getTypeChecker();
const autoImportProvider = host.getPackageJsonAutoImportProvider?.();
const autoImportProviderChecker = autoImportProvider?.getTypeChecker();
const newEntries = resolvingModuleSpecifiers( const newEntries = resolvingModuleSpecifiers(
"continuePreviousIncompleteResponse", "continuePreviousIncompleteResponse",
host, host,
@ -320,7 +321,7 @@ namespace ts.Completions {
/*isForImportStatementCompletion*/ false, /*isForImportStatementCompletion*/ false,
context => { context => {
const entries = mapDefined(previousResponse.entries, entry => { const entries = mapDefined(previousResponse.entries, entry => {
if (!entry.hasAction || !entry.source || !entry.data || entry.data.moduleSpecifier) { if (!entry.hasAction || !entry.source || !entry.data || completionEntryDataIsResolved(entry.data)) {
// Not an auto import or already resolved; keep as is // Not an auto import or already resolved; keep as is
return entry; return entry;
} }
@ -329,13 +330,8 @@ namespace ts.Completions {
return undefined; return undefined;
} }
const { symbol, origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host)); const { origin } = Debug.checkDefined(getAutoImportSymbolFromCompletionEntryData(entry.name, entry.data, program, host));
const info = exportMap.get( const info = exportMap.get(file.path, entry.data.exportMapKey);
file.path,
entry.name,
symbol,
origin.moduleSymbol.name,
origin.isFromPackageJson ? autoImportProviderChecker! : checker);
const result = info && context.tryResolve(info, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name))); const result = info && context.tryResolve(info, !isExternalModuleNameRelative(stripQuotes(origin.moduleSymbol.name)));
if (!result) return entry; if (!result) return entry;
@ -760,14 +756,56 @@ namespace ts.Completions {
return text.replace(/\$/gm, "\\$"); return text.replace(/\$/gm, "\\$");
} }
function originToCompletionEntryData(origin: SymbolOriginInfoExport): CompletionEntryData | undefined { function originToCompletionEntryData(origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport): CompletionEntryData | undefined {
return { const ambientModuleName = origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name);
const isPackageJsonImport = origin.isFromPackageJson ? true : undefined;
if (originIsResolvedExport(origin)) {
const resolvedData: CompletionEntryDataResolved = {
exportName: origin.exportName, exportName: origin.exportName,
moduleSpecifier: origin.moduleSpecifier,
ambientModuleName,
fileName: origin.fileName,
isPackageJsonImport,
};
return resolvedData;
}
const unresolvedData: CompletionEntryDataUnresolved = {
exportName: origin.exportName,
exportMapKey: origin.exportMapKey,
fileName: origin.fileName, fileName: origin.fileName,
ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name), ambientModuleName: origin.fileName ? undefined : stripQuotes(origin.moduleSymbol.name),
isPackageJsonImport: origin.isFromPackageJson ? true : undefined, isPackageJsonImport: origin.isFromPackageJson ? true : undefined,
moduleSpecifier: originIsResolvedExport(origin) ? origin.moduleSpecifier : undefined,
}; };
return unresolvedData;
}
function completionEntryDataToSymbolOriginInfo(data: CompletionEntryData, completionName: string, moduleSymbol: Symbol): SymbolOriginInfoExport | SymbolOriginInfoResolvedExport {
const isDefaultExport = data.exportName === InternalSymbolName.Default;
const isFromPackageJson = !!data.isPackageJsonImport;
if (completionEntryDataIsResolved(data)) {
const resolvedOrigin: SymbolOriginInfoResolvedExport = {
kind: SymbolOriginInfoKind.ResolvedExport,
exportName: data.exportName,
moduleSpecifier: data.moduleSpecifier,
symbolName: completionName,
fileName: data.fileName,
moduleSymbol,
isDefaultExport,
isFromPackageJson,
};
return resolvedOrigin;
}
const unresolvedOrigin: SymbolOriginInfoExport = {
kind: SymbolOriginInfoKind.Export,
exportName: data.exportName,
exportMapKey: data.exportMapKey,
symbolName: completionName,
fileName: data.fileName,
moduleSymbol,
isDefaultExport,
isFromPackageJson,
};
return unresolvedOrigin;
} }
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, contextToken: Node | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) { function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, contextToken: Node | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
@ -1762,21 +1800,37 @@ namespace ts.Completions {
const index = symbols.length; const index = symbols.length;
symbols.push(firstAccessibleSymbol); symbols.push(firstAccessibleSymbol);
const moduleSymbol = firstAccessibleSymbol.parent; const moduleSymbol = firstAccessibleSymbol.parent;
if (!moduleSymbol || !isExternalModuleSymbol(moduleSymbol)) { if (!moduleSymbol ||
!isExternalModuleSymbol(moduleSymbol) ||
typeChecker.tryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.name, moduleSymbol) !== firstAccessibleSymbol
) {
symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) }; symbolToOriginInfoMap[index] = { kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberNoExport) };
} }
else { else {
const origin: SymbolOriginInfoExport = { const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined;
const { moduleSpecifier } = codefix.getModuleSpecifierForBestExportInfo([{
exportKind: ExportKind.Named,
moduleFileName: fileName,
isFromPackageJson: false,
moduleSymbol,
symbol: firstAccessibleSymbol,
targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags,
}], sourceFile, program, host, preferences) || {};
if (moduleSpecifier) {
const origin: SymbolOriginInfoResolvedExport = {
kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport),
moduleSymbol, moduleSymbol,
isDefaultExport: false, isDefaultExport: false,
symbolName: firstAccessibleSymbol.name, symbolName: firstAccessibleSymbol.name,
exportName: firstAccessibleSymbol.name, exportName: firstAccessibleSymbol.name,
fileName: isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? cast(moduleSymbol.valueDeclaration, isSourceFile).fileName : undefined, fileName,
moduleSpecifier,
}; };
symbolToOriginInfoMap[index] = origin; symbolToOriginInfoMap[index] = origin;
} }
} }
}
else if (preferences.includeCompletionsWithInsertText) { else if (preferences.includeCompletionsWithInsertText) {
addSymbolOriginInfo(symbol); addSymbolOriginInfo(symbol);
addSymbolSortInfo(symbol); addSymbolSortInfo(symbol);
@ -2034,7 +2088,7 @@ namespace ts.Completions {
preferences, preferences,
!!importCompletionNode, !!importCompletionNode,
context => { context => {
exportInfo.forEach(sourceFile.path, (info, symbolName, isFromAmbientModule) => { exportInfo.forEach(sourceFile.path, (info, symbolName, isFromAmbientModule, exportMapKey) => {
if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return; if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return;
if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return; if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return;
// `targetFlags` should be the same for each `info` // `targetFlags` should be the same for each `info`
@ -2056,6 +2110,7 @@ namespace ts.Completions {
kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export, kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export,
moduleSpecifier, moduleSpecifier,
symbolName, symbolName,
exportMapKey,
exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name, exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name,
fileName: exportInfo.moduleFileName, fileName: exportInfo.moduleFileName,
isDefaultExport, isDefaultExport,
@ -2974,7 +3029,7 @@ namespace ts.Completions {
return { contextToken: previousToken as Node, previousToken: previousToken as Node }; return { contextToken: previousToken as Node, previousToken: previousToken as Node };
} }
function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport } | undefined { function getAutoImportSymbolFromCompletionEntryData(name: string, data: CompletionEntryData, program: Program, host: LanguageServiceHost): { symbol: Symbol, origin: SymbolOriginInfoExport | SymbolOriginInfoResolvedExport } | undefined {
const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program; const containingProgram = data.isPackageJsonImport ? host.getPackageJsonAutoImportProvider!()! : program;
const checker = containingProgram.getTypeChecker(); const checker = containingProgram.getTypeChecker();
const moduleSymbol = const moduleSymbol =
@ -2989,18 +3044,7 @@ namespace ts.Completions {
if (!symbol) return undefined; if (!symbol) return undefined;
const isDefaultExport = data.exportName === InternalSymbolName.Default; const isDefaultExport = data.exportName === InternalSymbolName.Default;
symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol; symbol = isDefaultExport && getLocalSymbolForExportDefault(symbol) || symbol;
return { return { symbol, origin: completionEntryDataToSymbolOriginInfo(data, name, moduleSymbol) };
symbol,
origin: {
kind: data.moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export,
moduleSymbol,
symbolName: name,
isDefaultExport,
exportName: data.exportName,
fileName: data.fileName,
isFromPackageJson: !!data.isPackageJsonImport,
}
};
} }
interface CompletionEntryDisplayNameForSymbol { interface CompletionEntryDisplayNameForSymbol {

View file

@ -46,8 +46,8 @@ namespace ts {
isUsableByFile(importingFile: Path): boolean; isUsableByFile(importingFile: Path): boolean;
clear(): void; clear(): void;
add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, scriptTarget: ScriptTarget, checker: TypeChecker): void; add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, scriptTarget: ScriptTarget, checker: TypeChecker): void;
get(importingFile: Path, importedName: string, symbol: Symbol, moduleName: string, checker: TypeChecker): readonly SymbolExportInfo[] | undefined; get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined;
forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean) => void): void; forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean, key: string) => void): void;
releaseSymbols(): void; releaseSymbols(): void;
isEmpty(): boolean; isEmpty(): boolean;
/** @returns Whether the change resulted in the cache being cleared */ /** @returns Whether the change resulted in the cache being cleared */
@ -87,11 +87,12 @@ namespace ts {
: getNameForExportedSymbol(namedSymbol, scriptTarget); : getNameForExportedSymbol(namedSymbol, scriptTarget);
const moduleName = stripQuotes(moduleSymbol.name); const moduleName = stripQuotes(moduleSymbol.name);
const id = exportInfoId++; const id = exportInfoId++;
const target = skipAlias(symbol, checker);
const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol; const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol;
const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol; const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : moduleSymbol;
if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]); if (!storedSymbol || !storedModuleSymbol) symbols.set(id, [symbol, moduleSymbol]);
exportInfo.add(key(importedName, symbol, moduleName, checker), { exportInfo.add(key(importedName, symbol, isExternalModuleNameRelative(moduleName) ? undefined : moduleName, checker), {
id, id,
symbolTableKey, symbolTableKey,
symbolName: importedName, symbolName: importedName,
@ -99,22 +100,22 @@ namespace ts {
moduleFile, moduleFile,
moduleFileName: moduleFile?.fileName, moduleFileName: moduleFile?.fileName,
exportKind, exportKind,
targetFlags: skipAlias(symbol, checker).flags, targetFlags: target.flags,
isFromPackageJson, isFromPackageJson,
symbol: storedSymbol, symbol: storedSymbol,
moduleSymbol: storedModuleSymbol, moduleSymbol: storedModuleSymbol,
}); });
}, },
get: (importingFile, importedName, symbol, moduleName, checker) => { get: (importingFile, key) => {
if (importingFile !== usableByFileName) return; if (importingFile !== usableByFileName) return;
const result = exportInfo.get(key(importedName, symbol, moduleName, checker)); const result = exportInfo.get(key);
return result?.map(rehydrateCachedInfo); return result?.map(rehydrateCachedInfo);
}, },
forEach: (importingFile, action) => { forEach: (importingFile, action) => {
if (importingFile !== usableByFileName) return; if (importingFile !== usableByFileName) return;
exportInfo.forEach((info, key) => { exportInfo.forEach((info, key) => {
const { symbolName, ambientModuleName } = parseKey(key); const { symbolName, ambientModuleName } = parseKey(key);
action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName); action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName, key);
}); });
}, },
releaseSymbols: () => { releaseSymbols: () => {
@ -183,29 +184,18 @@ namespace ts {
}; };
} }
function key(importedName: string, symbol: Symbol, moduleName: string, checker: TypeChecker) { function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string {
const unquoted = stripQuotes(moduleName); const moduleKey = ambientModuleName || "";
const moduleKey = isExternalModuleNameRelative(unquoted) ? "/" : unquoted; return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`;
const target = skipAlias(symbol, checker);
return `${importedName}|${createSymbolKey(target)}|${moduleKey}`;
} }
function parseKey(key: string) { function parseKey(key: string) {
const symbolName = key.substring(0, key.indexOf("|")); const symbolName = key.substring(0, key.indexOf("|"));
const moduleKey = key.substring(key.lastIndexOf("|") + 1); const moduleKey = key.substring(key.lastIndexOf("|") + 1);
const ambientModuleName = moduleKey === "/" ? undefined : moduleKey; const ambientModuleName = moduleKey === "" ? undefined : moduleKey;
return { symbolName, ambientModuleName }; return { symbolName, ambientModuleName };
} }
function createSymbolKey(symbol: Symbol) {
let key = symbol.name;
while (symbol.parent) {
key += `,${symbol.parent.name}`;
symbol = symbol.parent;
}
return key;
}
function fileIsGlobalOnly(file: SourceFile) { function fileIsGlobalOnly(file: SourceFile) {
return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames; return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames;
} }

View file

@ -1187,24 +1187,32 @@ namespace ts {
entries: CompletionEntry[]; entries: CompletionEntry[];
} }
export interface CompletionEntryData { export interface CompletionEntryDataAutoImport {
/**
* The name of the property or export in the module's symbol table. Differs from the completion name
* in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
*/
exportName: string;
moduleSpecifier?: string;
/** The file name declaring the export's module symbol, if it was an external module */ /** The file name declaring the export's module symbol, if it was an external module */
fileName?: string; fileName?: string;
/** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */
ambientModuleName?: string; ambientModuleName?: string;
/** True if the export was found in the package.json AutoImportProvider */ /** True if the export was found in the package.json AutoImportProvider */
isPackageJsonImport?: true; isPackageJsonImport?: true;
/**
* The name of the property or export in the module's symbol table. Differs from the completion name
* in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
*/
exportName: string;
/**
* Set for auto imports with eagerly resolved module specifiers.
*/
moduleSpecifier?: string;
} }
export interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport {
/** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */
exportMapKey: string;
}
export interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport {
moduleSpecifier: string;
}
export type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved;
// see comments in protocol.ts // see comments in protocol.ts
export interface CompletionEntry { export interface CompletionEntry {
name: string; name: string;

View file

@ -42,8 +42,14 @@ namespace ts.projectSystem {
source: "/a", source: "/a",
sourceDisplay: undefined, sourceDisplay: undefined,
isSnippet: undefined, isSnippet: undefined,
data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined, moduleSpecifier: undefined } data: { exportName: "foo", fileName: "/a.ts", ambientModuleName: undefined, isPackageJsonImport: undefined }
}; };
// `data.exportMapKey` contains a SymbolId so should not be mocked up with an expected value here.
// Just assert that it's a string and then delete it so we can compare everything else with `deepEqual`.
const exportMapKey = (response?.entries[0].data as any)?.exportMapKey;
assert.isString(exportMapKey);
delete (response?.entries[0].data as any).exportMapKey;
assert.deepEqual<protocol.CompletionInfo | undefined>(response, { assert.deepEqual<protocol.CompletionInfo | undefined>(response, {
isGlobalCompletion: true, isGlobalCompletion: true,
isIncomplete: undefined, isIncomplete: undefined,
@ -55,7 +61,7 @@ namespace ts.projectSystem {
const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = { const detailsRequestArgs: protocol.CompletionDetailsRequestArgs = {
...requestLocation, ...requestLocation,
entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts" } }], entryNames: [{ name: "foo", source: "/a", data: { exportName: "foo", fileName: "/a.ts", exportMapKey } }],
}; };
const detailsResponse = executeSessionRequest<protocol.CompletionDetailsRequest, protocol.CompletionDetailsResponse>(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs); const detailsResponse = executeSessionRequest<protocol.CompletionDetailsRequest, protocol.CompletionDetailsResponse>(session, protocol.CommandTypes.CompletionDetails, detailsRequestArgs);

View file

@ -6367,23 +6367,28 @@ declare namespace ts {
isIncomplete?: true; isIncomplete?: true;
entries: CompletionEntry[]; entries: CompletionEntry[];
} }
interface CompletionEntryData { interface CompletionEntryDataAutoImport {
/**
* The name of the property or export in the module's symbol table. Differs from the completion name
* in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
*/
exportName: string;
moduleSpecifier?: string;
/** The file name declaring the export's module symbol, if it was an external module */ /** The file name declaring the export's module symbol, if it was an external module */
fileName?: string; fileName?: string;
/** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */
ambientModuleName?: string; ambientModuleName?: string;
/** True if the export was found in the package.json AutoImportProvider */ /** True if the export was found in the package.json AutoImportProvider */
isPackageJsonImport?: true; isPackageJsonImport?: true;
/**
* The name of the property or export in the module's symbol table. Differs from the completion name
* in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
*/
exportName: string;
/**
* Set for auto imports with eagerly resolved module specifiers.
*/
moduleSpecifier?: string;
} }
interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport {
/** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */
exportMapKey: string;
}
interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport {
moduleSpecifier: string;
}
type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved;
interface CompletionEntry { interface CompletionEntry {
name: string; name: string;
kind: ScriptElementKind; kind: ScriptElementKind;

View file

@ -6367,23 +6367,28 @@ declare namespace ts {
isIncomplete?: true; isIncomplete?: true;
entries: CompletionEntry[]; entries: CompletionEntry[];
} }
interface CompletionEntryData { interface CompletionEntryDataAutoImport {
/**
* The name of the property or export in the module's symbol table. Differs from the completion name
* in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
*/
exportName: string;
moduleSpecifier?: string;
/** The file name declaring the export's module symbol, if it was an external module */ /** The file name declaring the export's module symbol, if it was an external module */
fileName?: string; fileName?: string;
/** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */ /** The module name (with quotes stripped) of the export's module symbol, if it was an ambient module */
ambientModuleName?: string; ambientModuleName?: string;
/** True if the export was found in the package.json AutoImportProvider */ /** True if the export was found in the package.json AutoImportProvider */
isPackageJsonImport?: true; isPackageJsonImport?: true;
/**
* The name of the property or export in the module's symbol table. Differs from the completion name
* in the case of InternalSymbolName.ExportEquals and InternalSymbolName.Default.
*/
exportName: string;
/**
* Set for auto imports with eagerly resolved module specifiers.
*/
moduleSpecifier?: string;
} }
interface CompletionEntryDataUnresolved extends CompletionEntryDataAutoImport {
/** The key in the `ExportMapCache` where the completion entry's `SymbolExportInfo[]` is found */
exportMapKey: string;
}
interface CompletionEntryDataResolved extends CompletionEntryDataAutoImport {
moduleSpecifier: string;
}
type CompletionEntryData = CompletionEntryDataUnresolved | CompletionEntryDataResolved;
interface CompletionEntry { interface CompletionEntry {
name: string; name: string;
kind: ScriptElementKind; kind: ScriptElementKind;

View file

@ -0,0 +1,35 @@
/// <reference path="fourslash.ts" />
// @module: commonjs
// @Filename: /node_modules/antd/index.d.ts
//// declare function Table(): void;
//// export default Table;
// @Filename: /node_modules/rc-table/index.d.ts
//// declare function Table(): void;
//// export default Table;
// @Filename: /index.ts
//// Table/**/
verify.completions({
marker: "",
exact: completion.globalsPlus([{
name: "Table",
source: "antd",
sourceDisplay: "antd",
sortText: completion.SortText.AutoImportSuggestions,
hasAction: true,
}, {
name: "Table",
source: "rc-table",
sourceDisplay: "rc-table",
sortText: completion.SortText.AutoImportSuggestions,
hasAction: true,
}]),
preferences: {
includeCompletionsForModuleExports: true,
allowIncompleteCompletions: true,
}
});

View file

@ -0,0 +1,33 @@
/// <reference path="fourslash.ts" />
// @esModuleInterop: true
// @Filename: /transient.d.ts
//// declare const map: { [K in "one"]: number };
//// export = map;
// @Filename: /r1.ts
//// export { one } from "./transient";
// @Filename: /r2.ts
//// export { one } from "./r1";
// @Filename: /index.ts
//// one/**/
goTo.marker("");
verify.completions({
marker: "",
exact: completion.globalsPlus([{
name: "one",
source: "./transient",
sourceDisplay: "./transient",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions,
}]),
preferences: {
includeCompletionsForModuleExports: true,
allowIncompleteCompletions: true,
}
});