Fix auto import completion inserting wrong module specifier (#41955)

* Don’t look for reëxports when import source was an ambient module

* Add additional assertion for clarity
This commit is contained in:
Andrew Branch 2020-12-18 15:13:02 -08:00 committed by GitHub
parent e84a95f707
commit 487be36919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 2 deletions

View File

@ -2798,7 +2798,12 @@ namespace FourSlash {
const details = this.getCompletionEntryDetails(options.name, options.source, options.preferences);
if (!details) {
return this.raiseError(`No completions were found for the given name, source, and preferences.`);
const completions = this.getCompletionListAtCaret(options.preferences)?.entries;
const matchingName = completions?.filter(e => e.name === options.name);
const detailMessage = matchingName?.length
? `\n Found ${matchingName.length} with name '${options.name}' from source(s) ${matchingName.map(e => `'${e.source}'`).join(", ")}.`
: "";
return this.raiseError(`No completions were found for the given name, source, and preferences.` + detailMessage);
}
const codeActions = details.codeActions;
if (codeActions?.length !== 1) {

View File

@ -210,7 +210,9 @@ namespace ts.codefix {
preferences: UserPreferences,
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
const compilerOptions = program.getCompilerOptions();
const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, /*useAutoImportProvider*/ true);
const exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name))
? [getSymbolExportInfoForSymbol(exportedSymbol, moduleSymbol, sourceFile, program, host)]
: getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, /*useAutoImportProvider*/ true);
const useRequire = shouldUseRequire(sourceFile, program);
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences)).moduleSpecifier;
@ -228,6 +230,27 @@ namespace ts.codefix {
return { description, changes, commands };
}
function getSymbolExportInfoForSymbol(symbol: Symbol, moduleSymbol: Symbol, importingFile: SourceFile, program: Program, host: LanguageServiceHost): SymbolExportInfo {
const compilerOptions = program.getCompilerOptions();
const mainProgramInfo = getInfoWithChecker(program.getTypeChecker());
if (mainProgramInfo) {
return mainProgramInfo;
}
const autoImportProvider = host.getPackageJsonAutoImportProvider?.()?.getTypeChecker();
return Debug.checkDefined(autoImportProvider && getInfoWithChecker(autoImportProvider), `Could not find symbol in specified module for code actions`);
function getInfoWithChecker(checker: TypeChecker): SymbolExportInfo | undefined {
const defaultInfo = getDefaultLikeExportInfo(importingFile, moduleSymbol, checker, compilerOptions);
if (defaultInfo && skipAlias(defaultInfo.symbol, checker) === symbol) {
return { moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(symbol, checker) };
}
const named = checker.tryGetMemberInModuleExportsAndProperties(symbol.name, moduleSymbol);
if (named && skipAlias(named, checker) === symbol) {
return { moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(symbol, checker) };
}
}
}
function getAllReExportingModules(importingFile: SourceFile, exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, host: LanguageServiceHost, program: Program, useAutoImportProvider: boolean): readonly SymbolExportInfo[] {
const result: SymbolExportInfo[] = [];
const compilerOptions = program.getCompilerOptions();

View File

@ -0,0 +1,57 @@
/// <reference path="fourslash.ts" />
// @module: commonjs
// @Filename: a.d.ts
//// declare namespace foo { class Bar {} }
//// declare module 'path1' {
//// import Bar = foo.Bar;
//// export default Bar;
//// }
//// declare module 'path2longer' {
//// import Bar = foo.Bar;
//// export {Bar};
//// }
////
// @Filename: b.ts
//// Ba/**/
verify.completions({
marker: "",
exact: [
completion.globalThisEntry,
...completion.globalsVars,
{
name: "foo",
sortText: completion.SortText.GlobalsOrKeywords
},
completion.undefinedVarEntry,
{
name: "Bar",
source: "path1",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
{
name: "Bar",
source: "path2longer",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
...completion.statementKeywordsWithTypes
],
preferences: {
includeCompletionsForModuleExports: true
}
});
verify.applyCodeActionFromCompletion("", {
name: "Bar",
source: "path2longer",
description: `Import 'Bar' from module "path2longer"`,
newFileContent: `import { Bar } from "path2longer";\n\nBa`,
preferences: {
includeCompletionsForModuleExports: true
}
});