-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Auto-imports: fix some exports being incorrectly stored as re-exports of others due to key conflict #45792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auto-imports: fix some exports being incorrectly stored as re-exports of others due to key conflict #45792
Changes from all commits
ba90719
00e2c65
106e1a9
20676c7
fef7e8c
6d35017
dcd0ed8
c4283e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,8 +46,8 @@ namespace ts { | |
isUsableByFile(importingFile: Path): boolean; | ||
clear(): 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; | ||
forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean) => void): void; | ||
get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The central insight of this PR is that the cache key does not actually need to be derivable from an updated program later on, because the only thing that ever calls |
||
forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean, key: string) => void): void; | ||
releaseSymbols(): void; | ||
isEmpty(): boolean; | ||
/** @returns Whether the change resulted in the cache being cleared */ | ||
|
@@ -87,34 +87,35 @@ namespace ts { | |
: getNameForExportedSymbol(namedSymbol, scriptTarget); | ||
const moduleName = stripQuotes(moduleSymbol.name); | ||
const id = exportInfoId++; | ||
const target = skipAlias(symbol, checker); | ||
const storedSymbol = symbol.flags & SymbolFlags.Transient ? undefined : symbol; | ||
const storedModuleSymbol = moduleSymbol.flags & SymbolFlags.Transient ? undefined : 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, | ||
symbolTableKey, | ||
symbolName: importedName, | ||
moduleName, | ||
moduleFile, | ||
moduleFileName: moduleFile?.fileName, | ||
exportKind, | ||
targetFlags: skipAlias(symbol, checker).flags, | ||
targetFlags: target.flags, | ||
isFromPackageJson, | ||
symbol: storedSymbol, | ||
moduleSymbol: storedModuleSymbol, | ||
}); | ||
}, | ||
get: (importingFile, importedName, symbol, moduleName, checker) => { | ||
get: (importingFile, key) => { | ||
if (importingFile !== usableByFileName) return; | ||
const result = exportInfo.get(key(importedName, symbol, moduleName, checker)); | ||
const result = exportInfo.get(key); | ||
return result?.map(rehydrateCachedInfo); | ||
}, | ||
forEach: (importingFile, action) => { | ||
if (importingFile !== usableByFileName) return; | ||
exportInfo.forEach((info, key) => { | ||
const { symbolName, ambientModuleName } = parseKey(key); | ||
action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName); | ||
action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName, key); | ||
}); | ||
}, | ||
releaseSymbols: () => { | ||
|
@@ -183,29 +184,18 @@ namespace ts { | |
}; | ||
} | ||
|
||
function key(importedName: string, symbol: Symbol, moduleName: string, checker: TypeChecker) { | ||
const unquoted = stripQuotes(moduleName); | ||
const moduleKey = isExternalModuleNameRelative(unquoted) ? "/" : unquoted; | ||
const target = skipAlias(symbol, checker); | ||
return `${importedName}|${createSymbolKey(target)}|${moduleKey}`; | ||
function key(importedName: string, symbol: Symbol, ambientModuleName: string | undefined, checker: TypeChecker): string { | ||
const moduleKey = ambientModuleName || ""; | ||
return `${importedName}|${getSymbolId(skipAlias(symbol, checker))}|${moduleKey}`; | ||
} | ||
|
||
function parseKey(key: string) { | ||
const symbolName = key.substring(0, key.indexOf("|")); | ||
const moduleKey = key.substring(key.lastIndexOf("|") + 1); | ||
const ambientModuleName = moduleKey === "/" ? undefined : moduleKey; | ||
const ambientModuleName = moduleKey === "" ? undefined : moduleKey; | ||
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) { | ||
return !file.commonJsModuleIndicator && !file.externalModuleIndicator && !file.moduleAugmentations && !file.ambientModuleNames; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of the larger set of changes in the PR, but it’s kind of a drive-by fix for a probable ancient bug that the real change, the addition of
exportMapKey
toSymbolOriginInfoExport
, forced me to consider. This has always been a weird ad-hoc auto-import for when you need to import a symbol in order to do a property completion, as shown in this test.I think there have likely been cases where fetching the actual auto-import (in completion details) for this completion would have failed due to some oversimplified assumptions here. I’m now guarding against that a bit more by resolving its module specifier up front and ensuring that the symbol can definitely be re-retrieved by looking for its name in the exports of the module. I could have sort of ignored this problem, but it felt better to deal with it.