Skip to content

Commit 4dd23b8

Browse files
committed
Add resolveLibrary method on hosts so that library resolution can be reused
Fixes #52759, #52707
1 parent a91f250 commit 4dd23b8

21 files changed

+1103
-1155
lines changed

src/compiler/moduleNameResolver.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1249,13 +1249,14 @@ function createModuleOrTypeReferenceResolutionCache<T>(
12491249
export function createModuleResolutionCache(
12501250
currentDirectory: string,
12511251
getCanonicalFileName: (s: string) => string,
1252-
options?: CompilerOptions
1252+
options?: CompilerOptions,
1253+
packageJsonInfoCache?: PackageJsonInfoCache,
12531254
): ModuleResolutionCache {
12541255
const result = createModuleOrTypeReferenceResolutionCache(
12551256
currentDirectory,
12561257
getCanonicalFileName,
12571258
options,
1258-
/*packageJsonInfoCache*/ undefined,
1259+
packageJsonInfoCache,
12591260
getOriginalOrResolvedModuleFileName,
12601261
) as ModuleResolutionCache;
12611262
result.getOrCreateCacheForModuleName = (nonRelativeName, mode, redirectedReference) => result.getOrCreateCacheForNonRelativeName(nonRelativeName, mode, redirectedReference);
@@ -1277,6 +1278,15 @@ export function createTypeReferenceDirectiveResolutionCache(
12771278
);
12781279
}
12791280

1281+
/** @internal */
1282+
export function getOptionsForLibraryResolution(options: CompilerOptions) {
1283+
return { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution };
1284+
}
1285+
1286+
export function resolveLibrary(libraryName: string, resolveFrom: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
1287+
return resolveModuleName(libraryName, resolveFrom, getOptionsForLibraryResolution(compilerOptions), host, cache);
1288+
}
1289+
12801290
export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined {
12811291
const containingDirectory = getDirectoryPath(containingFile);
12821292
return cache.getFromDirectoryCache(moduleName, mode, containingDirectory, /*redirectedReference*/ undefined);

src/compiler/program.ts

+101-17
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ import {
156156
HasChangedAutomaticTypeDirectiveNames,
157157
hasChangesInResolutions,
158158
hasExtension,
159+
HasInvalidatedLibResolutions,
159160
HasInvalidatedResolutions,
160161
hasJSDocNodes,
161162
hasJSFileExtension,
@@ -211,6 +212,7 @@ import {
211212
JsxEmit,
212213
length,
213214
libMap,
215+
LibResolution,
214216
libs,
215217
mapDefined,
216218
mapDefinedIterator,
@@ -276,6 +278,7 @@ import {
276278
ResolvedModuleWithFailedLookupLocations,
277279
ResolvedProjectReference,
278280
ResolvedTypeReferenceDirectiveWithFailedLookupLocations,
281+
resolveLibrary,
279282
resolveModuleName,
280283
resolveTypeReferenceDirective,
281284
returnFalse,
@@ -1049,6 +1052,12 @@ export function loadWithModeAwareCache<Entry, SourceFile, ResolutionCache, Resol
10491052
return resolutions;
10501053
}
10511054

1055+
function getLibFileName(libReference: FileReference) {
1056+
const libName = toFileNameLowerCase(libReference.fileName);
1057+
const libFileName = libMap.get(libName);
1058+
return { libName, libFileName };
1059+
}
1060+
10521061
/** @internal */
10531062
export function forEachResolvedProjectReference<T>(
10541063
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
@@ -1097,6 +1106,11 @@ function forEachProjectReference<T>(
10971106
/** @internal */
10981107
export const inferredTypesContainingFile = "__inferred type names__.ts";
10991108

1109+
/** @internal */
1110+
export function getInferredLibrarayNameResolveFrom(currentDirectory: string, libFileName: string) {
1111+
return combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
1112+
}
1113+
11001114
interface DiagnosticCache<T extends Diagnostic> {
11011115
perFile?: Map<Path, readonly T[]>;
11021116
allDiagnostics?: readonly T[];
@@ -1176,6 +1190,7 @@ export function isProgramUptoDate(
11761190
getSourceVersion: (path: Path, fileName: string) => string | undefined,
11771191
fileExists: (fileName: string) => boolean,
11781192
hasInvalidatedResolutions: HasInvalidatedResolutions,
1193+
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
11791194
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
11801195
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
11811196
projectReferences: readonly ProjectReference[] | undefined
@@ -1201,6 +1216,8 @@ export function isProgramUptoDate(
12011216
// If the compilation settings do no match, then the program is not up-to-date
12021217
if (!compareDataObjects(currentOptions, newOptions)) return false;
12031218

1219+
if (some(newOptions.lib, hasInvalidatedLibResolutions)) return false;
1220+
12041221
// If everything matches but the text of config file is changed,
12051222
// error locations can change for program options, so update the program
12061223
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
@@ -1209,7 +1226,11 @@ export function isProgramUptoDate(
12091226

12101227
function sourceFileNotUptoDate(sourceFile: SourceFile) {
12111228
return !sourceFileVersionUptoDate(sourceFile) ||
1212-
hasInvalidatedResolutions(sourceFile.path);
1229+
hasInvalidatedResolutions(sourceFile.path) ||
1230+
some(sourceFile.libReferenceDirectives, libRef => {
1231+
const { libFileName } = getLibFileName(libRef);
1232+
return !!libFileName && hasInvalidatedLibResolutions(libFileName);
1233+
});
12131234
}
12141235

12151236
function sourceFileVersionUptoDate(sourceFile: SourceFile) {
@@ -1469,7 +1490,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
14691490
let automaticTypeDirectiveNames: string[] | undefined;
14701491
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
14711492

1472-
let resolvedLibReferences: Map<string, string> | undefined;
1493+
let resolvedLibReferences: Map<string, LibResolution> | undefined;
1494+
let resolvedLibProcessing: Map<string, LibResolution> | undefined;
14731495

14741496
// The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules.
14751497
// This works as imported modules are discovered recursively in a depth first manner, specifically:
@@ -1594,6 +1616,17 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
15941616
);
15951617
}
15961618

1619+
const hasInvalidatedLibResolutions = host.hasInvalidatedLibResolutions || returnFalse;
1620+
let actualResolveLibrary: (libraryName: string, resolveFrom: string, options: CompilerOptions, libFileName: string) => ResolvedModuleWithFailedLookupLocations;
1621+
if (host.resolveLibrary) {
1622+
actualResolveLibrary = host.resolveLibrary.bind(host);
1623+
}
1624+
else {
1625+
const libraryResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options, moduleResolutionCache?.getPackageJsonInfoCache());
1626+
actualResolveLibrary = (libraryName, resolveFrom, options) =>
1627+
resolveLibrary(libraryName, resolveFrom, options, host, libraryResolutionCache);
1628+
}
1629+
15971630
// Map from a stringified PackageId to the source file with that id.
15981631
// Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile).
15991632
// `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around.
@@ -1774,6 +1807,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
17741807

17751808
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
17761809
oldProgram = undefined;
1810+
resolvedLibProcessing = undefined;
17771811

17781812
const program: Program = {
17791813
getRootFileNames: () => rootNames,
@@ -2434,6 +2468,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24342468
else {
24352469
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
24362470
}
2471+
// Do this resolution if necessary to determine reconstruction of program
2472+
if (structureIsReused !== StructureIsReused.Completely &&
2473+
some(newSourceFile.libReferenceDirectives, libReference => {
2474+
const { libFileName } = getLibFileName(libReference);
2475+
return !!libFileName &&
2476+
pathForLibFileWorker(libFileName).actual !== oldProgram?.resolvedLibReferences?.get(libFileName)?.actual;
2477+
})) {
2478+
structureIsReused = StructureIsReused.SafeModules;
2479+
}
24372480
}
24382481

24392482
if (structureIsReused !== StructureIsReused.Completely) {
@@ -2444,6 +2487,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24442487
return StructureIsReused.SafeModules;
24452488
}
24462489

2490+
if (some(options.lib, libFileName => pathForLibFileWorker(libFileName).actual !== oldProgram?.resolvedLibReferences?.get(libFileName)?.actual)) {
2491+
return StructureIsReused.SafeModules;
2492+
}
2493+
24472494
if (host.hasChangedAutomaticTypeDirectiveNames) {
24482495
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
24492496
}
@@ -2600,7 +2647,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26002647
return equalityComparer(file.fileName, getDefaultLibraryFileName());
26012648
}
26022649
else {
2603-
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!));
2650+
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
26042651
}
26052652
}
26062653

@@ -3314,11 +3361,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
33143361
}
33153362

33163363
function getLibFileFromReference(ref: FileReference) {
3317-
const libName = toFileNameLowerCase(ref.fileName);
3318-
const libFileName = libMap.get(libName);
3319-
if (libFileName) {
3320-
return getSourceFile(resolvedLibReferences?.get(libFileName)!);
3321-
}
3364+
const { libFileName } = getLibFileName(ref);
3365+
const actualFileName = libFileName && resolvedLibReferences?.get(libFileName)?.actual;
3366+
return actualFileName !== undefined ? getSourceFile(actualFileName) : undefined;
33223367
}
33233368

33243369
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
@@ -3815,7 +3860,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
38153860

38163861
function pathForLibFile(libFileName: string): string {
38173862
const existing = resolvedLibReferences?.get(libFileName);
3818-
if (existing) return existing;
3863+
if (existing) return existing.actual;
3864+
const result = pathForLibFileWorker(libFileName);
3865+
(resolvedLibReferences ??= new Map()).set(libFileName, result);
3866+
return result.actual;
3867+
}
3868+
3869+
function getLibraryName(libFileName: string) {
38193870
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
38203871
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
38213872
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
@@ -3826,19 +3877,52 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
38263877
path += (i === 2 ? "/" : "-") + components[i];
38273878
i++;
38283879
}
3829-
const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
3830-
const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution }, host, moduleResolutionCache);
3831-
const result = localOverrideModuleResult?.resolvedModule ?
3832-
localOverrideModuleResult.resolvedModule.resolvedFileName :
3833-
combinePaths(defaultLibraryPath, libFileName);
3834-
(resolvedLibReferences ??= new Map()).set(libFileName, result);
3880+
return "@typescript/lib-" + path;
3881+
}
3882+
3883+
function pathForLibFileWorker(libFileName: string): LibResolution {
3884+
const existing = resolvedLibProcessing?.get(libFileName);
3885+
if (existing) return existing;
3886+
3887+
if (structureIsReused !== StructureIsReused.Not && oldProgram && !hasInvalidatedLibResolutions(libFileName)) {
3888+
const oldResolution = oldProgram.resolvedLibReferences?.get(libFileName);
3889+
if (oldResolution) {
3890+
if (oldResolution.resolution && isTraceEnabled(options, host)) {
3891+
const libraryName = getLibraryName(libFileName);
3892+
const resolveFrom = getInferredLibrarayNameResolveFrom(currentDirectory, libFileName);
3893+
trace(host,
3894+
oldResolution.resolution.resolvedModule ?
3895+
oldResolution.resolution.resolvedModule.packageId ?
3896+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
3897+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
3898+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
3899+
libraryName,
3900+
getNormalizedAbsolutePath(resolveFrom, currentDirectory),
3901+
oldResolution.resolution.resolvedModule?.resolvedFileName,
3902+
oldResolution.resolution.resolvedModule?.packageId && packageIdToString(oldResolution.resolution.resolvedModule.packageId)
3903+
);
3904+
}
3905+
(resolvedLibProcessing ??= new Map()).set(libFileName, oldResolution);
3906+
return oldResolution;
3907+
}
3908+
}
3909+
3910+
const libraryName = getLibraryName(libFileName);
3911+
const resolveFrom = getInferredLibrarayNameResolveFrom(currentDirectory, libFileName);
3912+
const resolution = actualResolveLibrary(libraryName, resolveFrom, options, libFileName);
3913+
const result: LibResolution = {
3914+
resolution,
3915+
actual: resolution.resolvedModule ?
3916+
resolution.resolvedModule.resolvedFileName :
3917+
combinePaths(defaultLibraryPath, libFileName)
3918+
};
3919+
(resolvedLibProcessing ??= new Map()).set(libFileName, result);
38353920
return result;
38363921
}
38373922

38383923
function processLibReferenceDirectives(file: SourceFile) {
38393924
forEach(file.libReferenceDirectives, (libReference, index) => {
3840-
const libName = toFileNameLowerCase(libReference.fileName);
3841-
const libFileName = libMap.get(libName);
3925+
const { libName, libFileName } = getLibFileName(libReference);
38423926
if (libFileName) {
38433927
// we ignore any 'no-default-lib' reference set on this file.
38443928
processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, });

0 commit comments

Comments
 (0)