Skip to content

Commit 1e40dba

Browse files
committed
Pass in information for the module name resolution when resolutions from file are partially used
Fixes #48229
1 parent 9a36017 commit 1e40dba

File tree

10 files changed

+105
-72
lines changed

10 files changed

+105
-72
lines changed

src/compiler/moduleNameResolver.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,9 @@ namespace ts {
732732

733733
/* @internal */
734734
export function createModeAwareCache<T>(): ModeAwareCache<T> {
735-
const underlying = new Map<string, T>();
736-
const memoizedReverseKeys = new Map<string, [specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined]>();
735+
const underlying = new Map<ModeAwareCacheKey, T>();
736+
type ModeAwareCacheKey = string & { __modeAwareCacheKey: any; };
737+
const memoizedReverseKeys = new Map<ModeAwareCacheKey, [specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined]>();
737738

738739
const cache: ModeAwareCache<T> = {
739740
get(specifier, mode) {
@@ -763,7 +764,7 @@ namespace ts {
763764
return cache;
764765

765766
function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) {
766-
const result = mode === undefined ? specifier : `${mode}|${specifier}`;
767+
const result = (mode === undefined ? specifier : `${mode}|${specifier}`) as ModeAwareCacheKey;
767768
memoizedReverseKeys.set(result, [specifier, mode]);
768769
return result;
769770
}

src/compiler/program.ts

+38-20
Original file line numberDiff line numberDiff line change
@@ -1075,24 +1075,39 @@ namespace ts {
10751075

10761076
let moduleResolutionCache: ModuleResolutionCache | undefined;
10771077
let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined;
1078-
let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[];
1078+
let actualResolveModuleNamesWorker: (
1079+
moduleNames: string[],
1080+
containingFile: SourceFile,
1081+
containingFileName: string,
1082+
redirectedReference: ResolvedProjectReference | undefined,
1083+
partialResolutionInfo: PartialResolutionInfo | undefined,
1084+
) => ResolvedModuleFull[];
10791085
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
10801086
if (host.resolveModuleNames) {
1081-
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => {
1082-
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
1083-
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
1084-
return resolved as ResolvedModuleFull;
1085-
}
1086-
const withExtension = clone(resolved) as ResolvedModuleFull;
1087-
withExtension.extension = extensionFromPath(resolved.resolvedFileName);
1088-
return withExtension;
1089-
});
1087+
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, partialResolutionInfo) =>
1088+
host.resolveModuleNames!(
1089+
Debug.checkEachDefined(moduleNames),
1090+
containingFileName,
1091+
partialResolutionInfo?.reusedNames?.map(({ name }) => name),
1092+
redirectedReference,
1093+
options,
1094+
containingFile,
1095+
partialResolutionInfo
1096+
).map(resolved => {
1097+
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
1098+
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
1099+
return resolved as ResolvedModuleFull;
1100+
}
1101+
const withExtension = clone(resolved) as ResolvedModuleFull;
1102+
withExtension.extension = extensionFromPath(resolved.resolvedFileName);
1103+
return withExtension;
1104+
});
10901105
moduleResolutionCache = host.getModuleResolutionCache?.();
10911106
}
10921107
else {
10931108
moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options);
10941109
const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; // TODO: GH#18217
1095-
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader);
1110+
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference) => loadWithModeAwareCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader);
10961111
}
10971112

10981113
let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[];
@@ -1398,13 +1413,13 @@ namespace ts {
13981413
}
13991414
}
14001415

1401-
function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] {
1416+
function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, partialResolutionInfo: PartialResolutionInfo | undefined): readonly ResolvedModuleFull[] {
14021417
if (!moduleNames.length) return emptyArray;
14031418
const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory);
14041419
const redirectedReference = getRedirectReferenceForResolution(containingFile);
14051420
tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName });
14061421
performance.mark("beforeResolveModule");
1407-
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference);
1422+
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, redirectedReference, partialResolutionInfo);
14081423
performance.mark("afterResolveModule");
14091424
performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule");
14101425
tracing?.pop();
@@ -1511,7 +1526,7 @@ namespace ts {
15111526
if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) {
15121527
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
15131528
// the best we can do is fallback to the default logic.
1514-
return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined);
1529+
return resolveModuleNamesWorker(moduleNames, file, /*partialResolutionInfo*/ undefined);
15151530
}
15161531

15171532
const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName);
@@ -1540,6 +1555,7 @@ namespace ts {
15401555

15411556
/** An ordered list of module names for which we cannot recover the resolution. */
15421557
let unknownModuleNames: string[] | undefined;
1558+
let unknownModuleNamesIndex: number[] | undefined;
15431559
/**
15441560
* The indexing of elements in this list matches that of `moduleNames`.
15451561
*
@@ -1550,15 +1566,16 @@ namespace ts {
15501566
* * ResolvedModuleFull instance: can be reused.
15511567
*/
15521568
let result: (ResolvedModuleFull | undefined)[] | undefined;
1553-
let reusedNames: string[] | undefined;
1569+
let reusedNames: { name: string; mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined; }[] | undefined;
15541570
/** A transient placeholder used to mark predicted resolution in the result list. */
15551571
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any;
15561572

15571573
for (let i = 0; i < moduleNames.length; i++) {
15581574
const moduleName = moduleNames[i];
15591575
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
15601576
if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) {
1561-
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i));
1577+
const mode = getModeForResolutionAtIndex(oldSourceFile, i);
1578+
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, mode);
15621579
if (oldResolvedModule) {
15631580
if (isTraceEnabled(options, host)) {
15641581
trace(host,
@@ -1571,8 +1588,8 @@ namespace ts {
15711588
oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId)
15721589
);
15731590
}
1574-
(result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule;
1575-
(reusedNames || (reusedNames = [])).push(moduleName);
1591+
(result ??= new Array(moduleNames.length))[i] = oldResolvedModule;
1592+
(reusedNames ??= []).push({ name: moduleName, mode });
15761593
continue;
15771594
}
15781595
}
@@ -1596,12 +1613,13 @@ namespace ts {
15961613
}
15971614
else {
15981615
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
1599-
(unknownModuleNames || (unknownModuleNames = [])).push(moduleName);
1616+
(unknownModuleNames ??= []).push(moduleName);
1617+
(unknownModuleNamesIndex ??= []).push(i);
16001618
}
16011619
}
16021620

16031621
const resolutions = unknownModuleNames && unknownModuleNames.length
1604-
? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames)
1622+
? resolveModuleNamesWorker(unknownModuleNames, file, { reusedNames, namesIndex: unknownModuleNamesIndex! })
16051623
: emptyArray;
16061624

16071625
// Combine results of resolutions and predicted results

src/compiler/resolutionCache.ts

+28-13
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ namespace ts {
55
startRecordingFilesWithChangedResolutions(): void;
66
finishRecordingFilesWithChangedResolutions(): Path[] | undefined;
77

8-
resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[];
8+
resolveModuleNames(
9+
moduleNames: string[],
10+
containingFile: string,
11+
redirectedReference: ResolvedProjectReference | undefined,
12+
containingSourceFile: SourceFile | undefined,
13+
partialResolutionInfo: PartialResolutionInfo | undefined
14+
): (ResolvedModuleFull | undefined)[];
915
getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined;
1016
resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[];
1117

@@ -414,7 +420,7 @@ namespace ts {
414420
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T;
415421
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
416422
shouldRetryResolution: (t: T) => boolean;
417-
reusedNames?: readonly string[];
423+
partialResolutionInfo?: PartialResolutionInfo;
418424
logChanges?: boolean;
419425
containingSourceFile?: SourceFile;
420426
containingSourceFileMode?: SourceFile["impliedNodeFormat"];
@@ -423,7 +429,7 @@ namespace ts {
423429
names, containingFile, redirectedReference,
424430
cache, perDirectoryCacheWithRedirects,
425431
loader, getResolutionWithResolvedFileName,
426-
shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode
432+
shouldRetryResolution, partialResolutionInfo, logChanges, containingSourceFile, containingSourceFileMode
427433
}: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] {
428434
const path = resolutionHost.toPath(containingFile);
429435
const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!;
@@ -455,7 +461,7 @@ namespace ts {
455461
// Type references instead supply a `containingSourceFileMode` and a non-string entry which contains
456462
// a default file mode override if applicable.
457463
const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) :
458-
containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined;
464+
containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, partialResolutionInfo?.namesIndex[i] ?? i) : undefined;
459465
i++;
460466
let resolution = resolutionsInFile.get(name, mode);
461467
// Resolution is valid if it is present and not invalidated
@@ -539,13 +545,16 @@ namespace ts {
539545
resolvedModules.push(getResolutionWithResolvedFileName(resolution));
540546
}
541547

542-
// Stop watching and remove the unused name
543-
resolutionsInFile.forEach((resolution, name, mode) => {
544-
if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) {
545-
stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
546-
resolutionsInFile.delete(name, mode);
547-
}
548-
});
548+
partialResolutionInfo?.reusedNames?.forEach(({ name, mode }) => seenNamesInFile.set(name, mode, true));
549+
if (resolutionsInFile.size() !== seenNamesInFile.size()) {
550+
// Stop watching and remove the unused name
551+
resolutionsInFile.forEach((resolution, name, mode) => {
552+
if (!seenNamesInFile.has(name, mode)) {
553+
stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
554+
resolutionsInFile.delete(name, mode);
555+
}
556+
});
557+
}
549558

550559
return resolvedModules;
551560

@@ -582,7 +591,13 @@ namespace ts {
582591
});
583592
}
584593

585-
function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] {
594+
function resolveModuleNames(
595+
moduleNames: string[],
596+
containingFile: string,
597+
redirectedReference?: ResolvedProjectReference,
598+
containingSourceFile?: SourceFile,
599+
partialResolutionInfo?: PartialResolutionInfo
600+
): (ResolvedModuleFull | undefined)[] {
586601
return resolveNamesWithLocalCache<CachedResolvedModuleWithFailedLookupLocations, ResolvedModuleFull>({
587602
names: moduleNames,
588603
containingFile,
@@ -592,7 +607,7 @@ namespace ts {
592607
loader: resolveModuleName,
593608
getResolutionWithResolvedFileName: getResolvedModule,
594609
shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
595-
reusedNames,
610+
partialResolutionInfo,
596611
logChanges: logChangesWhenResolvingModule,
597612
containingSourceFile,
598613
});

src/compiler/types.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -6948,6 +6948,11 @@ namespace ts {
69486948
/* @internal */
69496949
export type HasChangedAutomaticTypeDirectiveNames = () => boolean;
69506950

6951+
export interface PartialResolutionInfo {
6952+
reusedNames: { name: string; mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined; }[] | undefined;
6953+
namesIndex: readonly number[];
6954+
}
6955+
69516956
export interface CompilerHost extends ModuleResolutionHost {
69526957
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
69536958
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
@@ -6968,7 +6973,7 @@ namespace ts {
69686973
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
69696974
* 'throw new Error("NotImplemented")'
69706975
*/
6971-
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[];
6976+
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, partialResolutionInfo?: PartialResolutionInfo): (ResolvedModule | undefined)[];
69726977
/**
69736978
* Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it
69746979
*/

0 commit comments

Comments
 (0)