Skip to content

Commit 7b51086

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

File tree

10 files changed

+105
-75
lines changed

10 files changed

+105
-75
lines changed

src/compiler/moduleNameResolver.ts

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

738738
/* @internal */
739739
export function createModeAwareCache<T>(): ModeAwareCache<T> {
740-
const underlying = new Map<string, T>();
741-
const memoizedReverseKeys = new Map<string, [specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined]>();
740+
const underlying = new Map<ModeAwareCacheKey, T>();
741+
type ModeAwareCacheKey = string & { __modeAwareCacheKey: any; };
742+
const memoizedReverseKeys = new Map<ModeAwareCacheKey, [specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined]>();
742743

743744
const cache: ModeAwareCache<T> = {
744745
get(specifier, mode) {
@@ -768,7 +769,7 @@ namespace ts {
768769
return cache;
769770

770771
function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) {
771-
const result = mode === undefined ? specifier : `${mode}|${specifier}`;
772+
const result = (mode === undefined ? specifier : `${mode}|${specifier}`) as ModeAwareCacheKey;
772773
memoizedReverseKeys.set(result, [specifier, mode]);
773774
return result;
774775
}

src/compiler/program.ts

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

10611061
let moduleResolutionCache: ModuleResolutionCache | undefined;
10621062
let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined;
1063-
let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[];
1063+
let actualResolveModuleNamesWorker: (
1064+
moduleNames: string[],
1065+
containingFile: SourceFile,
1066+
containingFileName: string,
1067+
redirectedReference: ResolvedProjectReference | undefined,
1068+
partialResolutionInfo: PartialResolutionInfo | undefined,
1069+
) => ResolvedModuleFull[];
10641070
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
10651071
if (host.resolveModuleNames) {
1066-
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => {
1067-
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
1068-
if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) {
1069-
return resolved as ResolvedModuleFull;
1070-
}
1071-
const withExtension = clone(resolved) as ResolvedModuleFull;
1072-
withExtension.extension = extensionFromPath(resolved.resolvedFileName);
1073-
return withExtension;
1074-
});
1072+
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference, partialResolutionInfo) =>
1073+
host.resolveModuleNames!(
1074+
Debug.checkEachDefined(moduleNames),
1075+
containingFileName,
1076+
partialResolutionInfo?.reusedNames?.map(({ name }) => name),
1077+
redirectedReference,
1078+
options,
1079+
containingFile,
1080+
partialResolutionInfo
1081+
).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+
});
10751090
moduleResolutionCache = host.getModuleResolutionCache?.();
10761091
}
10771092
else {
10781093
moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options);
10791094
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
1080-
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader);
1095+
actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, redirectedReference) => loadWithModeAwareCache<ResolvedModuleFull>(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader);
10811096
}
10821097

10831098
let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[];
@@ -1383,13 +1398,13 @@ namespace ts {
13831398
}
13841399
}
13851400

1386-
function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] {
1401+
function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, partialResolutionInfo: PartialResolutionInfo | undefined): readonly ResolvedModuleFull[] {
13871402
if (!moduleNames.length) return emptyArray;
13881403
const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory);
13891404
const redirectedReference = getRedirectReferenceForResolution(containingFile);
13901405
tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName });
13911406
performance.mark("beforeResolveModule");
1392-
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference);
1407+
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, redirectedReference, partialResolutionInfo);
13931408
performance.mark("afterResolveModule");
13941409
performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule");
13951410
tracing?.pop();
@@ -1496,7 +1511,7 @@ namespace ts {
14961511
if (structureIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) {
14971512
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
14981513
// the best we can do is fallback to the default logic.
1499-
return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined);
1514+
return resolveModuleNamesWorker(moduleNames, file, /*partialResolutionInfo*/ undefined);
15001515
}
15011516

15021517
const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName);
@@ -1525,6 +1540,7 @@ namespace ts {
15251540

15261541
/** An ordered list of module names for which we cannot recover the resolution. */
15271542
let unknownModuleNames: string[] | undefined;
1543+
let unknownModuleNamesIndex: number[] | undefined;
15281544
/**
15291545
* The indexing of elements in this list matches that of `moduleNames`.
15301546
*
@@ -1535,15 +1551,16 @@ namespace ts {
15351551
* * ResolvedModuleFull instance: can be reused.
15361552
*/
15371553
let result: ResolvedModuleFull[] | undefined;
1538-
let reusedNames: string[] | undefined;
1554+
let reusedNames: { name: string; mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined; }[] | undefined;
15391555
/** A transient placeholder used to mark predicted resolution in the result list. */
15401556
const predictedToResolveToAmbientModuleMarker: ResolvedModuleFull = {} as any;
15411557

15421558
for (let i = 0; i < moduleNames.length; i++) {
15431559
const moduleName = moduleNames[i];
15441560
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
15451561
if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) {
1546-
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i));
1562+
const mode = getModeForResolutionAtIndex(oldSourceFile, i);
1563+
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, mode);
15471564
if (oldResolvedModule) {
15481565
if (isTraceEnabled(options, host)) {
15491566
trace(host,
@@ -1556,8 +1573,8 @@ namespace ts {
15561573
oldResolvedModule.packageId && packageIdToString(oldResolvedModule.packageId)
15571574
);
15581575
}
1559-
(result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule;
1560-
(reusedNames || (reusedNames = [])).push(moduleName);
1576+
(result ??= new Array(moduleNames.length))[i] = oldResolvedModule;
1577+
(reusedNames ??= []).push({ name: moduleName, mode });
15611578
continue;
15621579
}
15631580
}
@@ -1581,12 +1598,13 @@ namespace ts {
15811598
}
15821599
else {
15831600
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
1584-
(unknownModuleNames || (unknownModuleNames = [])).push(moduleName);
1601+
(unknownModuleNames ??= []).push(moduleName);
1602+
(unknownModuleNamesIndex ??= []).push(i);
15851603
}
15861604
}
15871605

15881606
const resolutions = unknownModuleNames && unknownModuleNames.length
1589-
? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames)
1607+
? resolveModuleNamesWorker(unknownModuleNames, file, { reusedNames, namesIndex: unknownModuleNamesIndex! })
15901608
: emptyArray;
15911609

15921610
// 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

@@ -384,7 +390,7 @@ namespace ts {
384390
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T;
385391
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
386392
shouldRetryResolution: (t: T) => boolean;
387-
reusedNames?: readonly string[];
393+
partialResolutionInfo?: PartialResolutionInfo;
388394
logChanges?: boolean;
389395
containingSourceFile?: SourceFile;
390396
containingSourceFileMode?: SourceFile["impliedNodeFormat"];
@@ -393,7 +399,7 @@ namespace ts {
393399
names, containingFile, redirectedReference,
394400
cache, perDirectoryCacheWithRedirects,
395401
loader, getResolutionWithResolvedFileName,
396-
shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode
402+
shouldRetryResolution, partialResolutionInfo, logChanges, containingSourceFile, containingSourceFileMode
397403
}: ResolveNamesWithLocalCacheInput<T, R>): (R | undefined)[] {
398404
const path = resolutionHost.toPath(containingFile);
399405
const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!;
@@ -425,7 +431,7 @@ namespace ts {
425431
// Type references instead supply a `containingSourceFileMode` and a non-string entry which contains
426432
// a default file mode override if applicable.
427433
const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) :
428-
containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined;
434+
containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, partialResolutionInfo?.namesIndex[i] ?? i) : undefined;
429435
i++;
430436
let resolution = resolutionsInFile.get(name, mode);
431437
// Resolution is valid if it is present and not invalidated
@@ -509,13 +515,16 @@ namespace ts {
509515
resolvedModules.push(getResolutionWithResolvedFileName(resolution));
510516
}
511517

512-
// Stop watching and remove the unused name
513-
resolutionsInFile.forEach((resolution, name, mode) => {
514-
if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) {
515-
stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
516-
resolutionsInFile.delete(name, mode);
517-
}
518-
});
518+
partialResolutionInfo?.reusedNames?.forEach(({ name, mode }) => seenNamesInFile.set(name, mode, true));
519+
if (resolutionsInFile.size() !== seenNamesInFile.size()) {
520+
// Stop watching and remove the unused name
521+
resolutionsInFile.forEach((resolution, name, mode) => {
522+
if (!seenNamesInFile.has(name, mode)) {
523+
stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName);
524+
resolutionsInFile.delete(name, mode);
525+
}
526+
});
527+
}
519528

520529
return resolvedModules;
521530

@@ -552,7 +561,13 @@ namespace ts {
552561
});
553562
}
554563

555-
function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] {
564+
function resolveModuleNames(
565+
moduleNames: string[],
566+
containingFile: string,
567+
redirectedReference?: ResolvedProjectReference,
568+
containingSourceFile?: SourceFile,
569+
partialResolutionInfo?: PartialResolutionInfo
570+
): (ResolvedModuleFull | undefined)[] {
556571
return resolveNamesWithLocalCache<CachedResolvedModuleWithFailedLookupLocations, ResolvedModuleFull>({
557572
names: moduleNames,
558573
containingFile,
@@ -562,7 +577,7 @@ namespace ts {
562577
loader: resolveModuleName,
563578
getResolutionWithResolvedFileName: getResolvedModule,
564579
shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
565-
reusedNames,
580+
partialResolutionInfo,
566581
logChanges: logChangesWhenResolvingModule,
567582
containingSourceFile,
568583
});

src/compiler/types.ts

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

6949+
export interface PartialResolutionInfo {
6950+
reusedNames: { name: string; mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined; }[] | undefined;
6951+
namesIndex: readonly number[];
6952+
}
6953+
69496954
export interface CompilerHost extends ModuleResolutionHost {
69506955
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
69516956
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
@@ -6966,7 +6971,7 @@ namespace ts {
69666971
* If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just
69676972
* 'throw new Error("NotImplemented")'
69686973
*/
6969-
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[];
6974+
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile, partialResolutionInfo?: PartialResolutionInfo): (ResolvedModule | undefined)[];
69706975
/**
69716976
* 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
69726977
*/

0 commit comments

Comments
 (0)