Skip to content

Commit f029a82

Browse files
authored
Filter auto imports by symbol flags before resolving module specifiers (#45168)
* Filter auto imports by symbol flags before resolving module specifiers * Don’t filter types out of import statement completions
1 parent a947bbb commit f029a82

File tree

4 files changed

+30
-24
lines changed

4 files changed

+30
-24
lines changed

src/services/codefixes/importFixes.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,11 @@ namespace ts.codefix {
231231
function getInfoWithChecker(checker: TypeChecker, isFromPackageJson: boolean): SymbolExportInfo | undefined {
232232
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
233233
if (defaultInfo && skipAlias(defaultInfo.symbol, checker) === symbol) {
234-
return { symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: undefined, exportKind: defaultInfo.exportKind, isTypeOnly: isTypeOnlySymbol(symbol, checker), isFromPackageJson };
234+
return { symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: undefined, exportKind: defaultInfo.exportKind, targetFlags: skipAlias(symbol, checker).flags, isFromPackageJson };
235235
}
236236
const named = checker.tryGetMemberInModuleExportsAndProperties(symbol.name, moduleSymbol);
237237
if (named && skipAlias(named, checker) === symbol) {
238-
return { symbol: named, moduleSymbol, moduleFileName: undefined, exportKind: ExportKind.Named, isTypeOnly: isTypeOnlySymbol(symbol, checker), isFromPackageJson };
238+
return { symbol: named, moduleSymbol, moduleFileName: undefined, exportKind: ExportKind.Named, targetFlags: skipAlias(symbol, checker).flags, isFromPackageJson };
239239
}
240240
}
241241
}
@@ -256,12 +256,12 @@ namespace ts.codefix {
256256

257257
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
258258
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, compilerOptions.target) === symbolName) && skipAlias(defaultInfo.symbol, checker) === exportedSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
259-
result.push({ symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: moduleFile?.fileName, exportKind: defaultInfo.exportKind, isTypeOnly: isTypeOnlySymbol(defaultInfo.symbol, checker), isFromPackageJson });
259+
result.push({ symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: moduleFile?.fileName, exportKind: defaultInfo.exportKind, targetFlags: skipAlias(defaultInfo.symbol, checker).flags, isFromPackageJson });
260260
}
261261

262262
for (const exported of checker.getExportsAndPropertiesOfModule(moduleSymbol)) {
263263
if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
264-
result.push({ symbol: exported, moduleSymbol, moduleFileName: moduleFile?.fileName, exportKind: ExportKind.Named, isTypeOnly: isTypeOnlySymbol(exported, checker), isFromPackageJson });
264+
result.push({ symbol: exported, moduleSymbol, moduleFileName: moduleFile?.fileName, exportKind: ExportKind.Named, targetFlags: skipAlias(exported, checker).flags, isFromPackageJson });
265265
}
266266
}
267267
});
@@ -294,10 +294,6 @@ namespace ts.codefix {
294294
return result && { ...result, computedWithoutCacheCount };
295295
}
296296

297-
function isTypeOnlySymbol(s: Symbol, checker: TypeChecker): boolean {
298-
return !(skipAlias(s, checker).flags & SymbolFlags.Value);
299-
}
300-
301297
function isTypeOnlyPosition(sourceFile: SourceFile, position: number) {
302298
return isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
303299
}
@@ -395,9 +391,9 @@ namespace ts.codefix {
395391
});
396392
}
397393

398-
function getExistingImportDeclarations({ moduleSymbol, exportKind, isTypeOnly: exportedSymbolIsTypeOnly }: SymbolExportInfo, checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions): readonly FixAddToExistingImportInfo[] {
394+
function getExistingImportDeclarations({ moduleSymbol, exportKind, targetFlags }: SymbolExportInfo, checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions): readonly FixAddToExistingImportInfo[] {
399395
// Can't use an es6 import for a type in JS.
400-
if (exportedSymbolIsTypeOnly && isSourceFileJS(importingFile)) return emptyArray;
396+
if (!(targetFlags & SymbolFlags.Value) && isSourceFileJS(importingFile)) return emptyArray;
401397
const importKind = getImportKind(importingFile, exportKind, compilerOptions);
402398
return mapDefined(importingFile.imports, (moduleSpecifier): FixAddToExistingImportInfo | undefined => {
403399
const i = importFromModuleSpecifier(moduleSpecifier);
@@ -462,7 +458,7 @@ namespace ts.codefix {
462458
computedWithoutCacheCount += computedWithoutCache ? 1 : 0;
463459
return moduleSpecifiers?.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
464460
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
465-
exportInfo.isTypeOnly && isJs && position !== undefined
461+
!(exportInfo.targetFlags & SymbolFlags.Value) && isJs && position !== undefined
466462
? { kind: ImportFixKind.ImportType, moduleSpecifier, position, exportInfo }
467463
: {
468464
kind: ImportFixKind.AddNew,
@@ -547,7 +543,7 @@ namespace ts.codefix {
547543
if (!umdSymbol) return undefined;
548544
const symbol = checker.getAliasedSymbol(umdSymbol);
549545
const symbolName = umdSymbol.name;
550-
const exportInfos: readonly SymbolExportInfo[] = [{ symbol: umdSymbol, moduleSymbol: symbol, moduleFileName: undefined, exportKind: ExportKind.UMD, isTypeOnly: false, isFromPackageJson: false }];
546+
const exportInfos: readonly SymbolExportInfo[] = [{ symbol: umdSymbol, moduleSymbol: symbol, moduleFileName: undefined, exportKind: ExportKind.UMD, targetFlags: symbol.flags, isFromPackageJson: false }];
551547
const useRequire = shouldUseRequire(sourceFile, program);
552548
const fixes = getImportFixes(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, useRequire, program, sourceFile, host, preferences);
553549
return { fixes, symbolName };
@@ -653,7 +649,7 @@ namespace ts.codefix {
653649
!toFile && packageJsonFilter.allowsImportingAmbientModule(moduleSymbol, moduleSpecifierResolutionHost)
654650
) {
655651
const checker = program.getTypeChecker();
656-
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { symbol: exportedSymbol, moduleSymbol, moduleFileName: toFile?.fileName, exportKind, isTypeOnly: isTypeOnlySymbol(exportedSymbol, checker), isFromPackageJson });
652+
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { symbol: exportedSymbol, moduleSymbol, moduleFileName: toFile?.fileName, exportKind, targetFlags: skipAlias(exportedSymbol, checker).flags, isFromPackageJson });
657653
}
658654
}
659655
forEachExternalModuleToImportFrom(program, host, useAutoImportProvider, (moduleSymbol, sourceFile, program, isFromPackageJson) => {

src/services/completions.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,7 @@ namespace ts.Completions {
14521452
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
14531453
const symbolToSortTextIdMap: SymbolSortTextIdMap = [];
14541454
const seenPropertySymbols = new Map<SymbolId, true>();
1455-
const isTypeOnly = isTypeOnlyCompletion();
1455+
const isTypeOnlyLocation = isTypeOnlyCompletion();
14561456
const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => {
14571457
return createModuleSpecifierResolutionHost(isFromPackageJson ? host.getPackageJsonAutoImportProvider!()! : program, host);
14581458
});
@@ -1506,7 +1506,7 @@ namespace ts.Completions {
15061506
isJsxInitializer,
15071507
insideJsDocTagTypeExpression,
15081508
symbolToSortTextIdMap,
1509-
isTypeOnlyLocation: isTypeOnly,
1509+
isTypeOnlyLocation,
15101510
isJsxIdentifierExpected,
15111511
importCompletionNode,
15121512
hasUnresolvedAutoImports,
@@ -1806,7 +1806,7 @@ namespace ts.Completions {
18061806
const scopeNode = getScopeNode(contextToken, adjustedPosition, sourceFile) || sourceFile;
18071807
isInSnippetScope = isSnippetScope(scopeNode);
18081808

1809-
const symbolMeanings = (isTypeOnly ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias;
1809+
const symbolMeanings = (isTypeOnlyLocation ? SymbolFlags.None : SymbolFlags.Value) | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias;
18101810

18111811
symbols = concatenate(symbols, typeChecker.getSymbolsInScope(scopeNode, symbolMeanings));
18121812
Debug.assertEachIsDefined(symbols, "getSymbolsInScope() should all be defined");
@@ -1829,7 +1829,7 @@ namespace ts.Completions {
18291829
}
18301830
}
18311831
collectAutoImports();
1832-
if (isTypeOnly) {
1832+
if (isTypeOnlyLocation) {
18331833
keywordFilters = contextToken && isAssertionExpression(contextToken.parent)
18341834
? KeywordCompletionFilters.TypeAssertionKeywords
18351835
: KeywordCompletionFilters.TypeKeywords;
@@ -1931,6 +1931,9 @@ namespace ts.Completions {
19311931
context => {
19321932
exportInfo.forEach(sourceFile.path, (info, symbolName, isFromAmbientModule) => {
19331933
if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return;
1934+
// `targetFlags` should be the same for each `info`
1935+
if (!isTypeOnlyLocation && !importCompletionNode && !(info[0].targetFlags & SymbolFlags.Value)) return;
1936+
if (isTypeOnlyLocation && !(info[0].targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return;
19341937
const isCompletionDetailsMatch = detailsEntryId && some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name));
19351938
if (isCompletionDetailsMatch || !detailsEntryId && charactersFuzzyMatchInString(symbolName, lowerCaseTokenText)) {
19361939
const defaultExportInfo = find(info, isImportableExportInfo);

src/services/exportInfoMap.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ namespace ts {
2020
/** Set if `moduleSymbol` is an external module, not an ambient module */
2121
moduleFileName: string | undefined;
2222
exportKind: ExportKind;
23-
/** If true, can't use an es6 import from a js file. */
24-
isTypeOnly: boolean;
23+
targetFlags: SymbolFlags;
2524
/** True if export was only found via the package.json AutoImportProvider (for telemetry). */
2625
isFromPackageJson: boolean;
2726
}
@@ -38,7 +37,7 @@ namespace ts {
3837
readonly moduleSymbol: Symbol | undefined;
3938
moduleFileName: string | undefined;
4039
exportKind: ExportKind;
41-
isTypeOnly: boolean;
40+
targetFlags: SymbolFlags;
4241
isFromPackageJson: boolean;
4342
}
4443

@@ -92,7 +91,7 @@ namespace ts {
9291
moduleFile,
9392
moduleFileName: moduleFile?.fileName,
9493
exportKind,
95-
isTypeOnly: !(skipAlias(symbol, checker).flags & SymbolFlags.Value),
94+
targetFlags: skipAlias(symbol, checker).flags,
9695
isFromPackageJson,
9796
symbol: storedSymbol,
9897
moduleSymbol: storedModuleSymbol,
@@ -143,15 +142,15 @@ namespace ts {
143142

144143
function rehydrateCachedInfo(info: CachedSymbolExportInfo): SymbolExportInfo {
145144
if (info.symbol && info.moduleSymbol) return info as SymbolExportInfo;
146-
const { id, exportKind, isTypeOnly, isFromPackageJson, moduleFileName } = info;
145+
const { id, exportKind, targetFlags, isFromPackageJson, moduleFileName } = info;
147146
const [cachedSymbol, cachedModuleSymbol] = symbols.get(id) || emptyArray;
148147
if (cachedSymbol && cachedModuleSymbol) {
149148
return {
150149
symbol: cachedSymbol,
151150
moduleSymbol: cachedModuleSymbol,
152151
moduleFileName,
153152
exportKind,
154-
isTypeOnly,
153+
targetFlags,
155154
isFromPackageJson,
156155
};
157156
}
@@ -173,7 +172,7 @@ namespace ts {
173172
moduleSymbol,
174173
moduleFileName,
175174
exportKind,
176-
isTypeOnly,
175+
targetFlags,
177176
isFromPackageJson,
178177
};
179178
}

tests/cases/fourslash/importStatementCompletions1.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
// @Filename: /mod.ts
44
//// export const foo = 0;
5+
//// export type Foo = number;
56

67
// @Filename: /index0.ts
78
//// [|import f/*0*/|]
@@ -32,6 +33,13 @@
3233
isSnippet: true,
3334
replacementSpan: test.ranges()[marker],
3435
sourceDisplay: "./mod",
36+
}, {
37+
name: "Foo",
38+
source: "./mod",
39+
insertText: `import { Foo$1 } from "./mod";`,
40+
isSnippet: true,
41+
replacementSpan: test.ranges()[marker],
42+
sourceDisplay: "./mod",
3543
}],
3644
preferences: {
3745
includeCompletionsForImportStatements: true,

0 commit comments

Comments
 (0)