Skip to content

Commit 092497e

Browse files
committed
Add resolveLibrary method on hosts so that library resolution can be reused
Fixes #52759, #52707
1 parent c1f7ca7 commit 092497e

21 files changed

+1073
-508
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

+94-14
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,
@@ -1176,6 +1185,7 @@ export function isProgramUptoDate(
11761185
getSourceVersion: (path: Path, fileName: string) => string | undefined,
11771186
fileExists: (fileName: string) => boolean,
11781187
hasInvalidatedResolutions: HasInvalidatedResolutions,
1188+
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
11791189
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
11801190
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
11811191
projectReferences: readonly ProjectReference[] | undefined
@@ -1201,6 +1211,8 @@ export function isProgramUptoDate(
12011211
// If the compilation settings do no match, then the program is not up-to-date
12021212
if (!compareDataObjects(currentOptions, newOptions)) return false;
12031213

1214+
if (some(newOptions.lib, hasInvalidatedLibResolutions)) return false;
1215+
12041216
// If everything matches but the text of config file is changed,
12051217
// error locations can change for program options, so update the program
12061218
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
@@ -1209,7 +1221,11 @@ export function isProgramUptoDate(
12091221

12101222
function sourceFileNotUptoDate(sourceFile: SourceFile) {
12111223
return !sourceFileVersionUptoDate(sourceFile) ||
1212-
hasInvalidatedResolutions(sourceFile.path);
1224+
hasInvalidatedResolutions(sourceFile.path) ||
1225+
some(sourceFile.libReferenceDirectives, libRef => {
1226+
const { libFileName } = getLibFileName(libRef);
1227+
return !!libFileName && hasInvalidatedLibResolutions(libFileName);
1228+
});
12131229
}
12141230

12151231
function sourceFileVersionUptoDate(sourceFile: SourceFile) {
@@ -1469,7 +1485,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
14691485
let automaticTypeDirectiveNames: string[] | undefined;
14701486
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
14711487

1472-
let resolvedLibReferences: Map<string, string> | undefined;
1488+
let resolvedLibReferences: Map<string, LibResolution> | undefined;
1489+
let resolvedLibProcessing: Map<string, LibResolution> | undefined;
14731490

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

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

17751803
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
17761804
oldProgram = undefined;
1805+
resolvedLibProcessing = undefined;
17771806

17781807
const program: Program = {
17791808
getRootFileNames: () => rootNames,
@@ -2434,6 +2463,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24342463
else {
24352464
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
24362465
}
2466+
// Do this resolution if necessary to determine reconstruction of program
2467+
if (structureIsReused !== StructureIsReused.Completely &&
2468+
some(newSourceFile.libReferenceDirectives, libReference => {
2469+
const { libFileName } = getLibFileName(libReference);
2470+
return !!libFileName &&
2471+
pathForLibFileWorker(libFileName).actual !== oldProgram?.resolvedLibReferences?.get(libFileName)?.actual;
2472+
})) {
2473+
structureIsReused = StructureIsReused.SafeModules;
2474+
}
24372475
}
24382476

24392477
if (structureIsReused !== StructureIsReused.Completely) {
@@ -2444,6 +2482,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24442482
return StructureIsReused.SafeModules;
24452483
}
24462484

2485+
if (some(options.lib, libFileName => pathForLibFileWorker(libFileName).actual !== oldProgram?.resolvedLibReferences?.get(libFileName)?.actual)) {
2486+
return StructureIsReused.SafeModules;
2487+
}
2488+
24472489
if (host.hasChangedAutomaticTypeDirectiveNames) {
24482490
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
24492491
}
@@ -2600,7 +2642,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26002642
return equalityComparer(file.fileName, getDefaultLibraryFileName());
26012643
}
26022644
else {
2603-
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!));
2645+
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
26042646
}
26052647
}
26062648

@@ -3314,10 +3356,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
33143356
}
33153357

33163358
function getLibFileFromReference(ref: FileReference) {
3317-
const libName = toFileNameLowerCase(ref.fileName);
3318-
const libFileName = libMap.get(libName);
3359+
const { libFileName } = getLibFileName(ref);
33193360
if (libFileName) {
3320-
return getSourceFile(resolvedLibReferences?.get(libFileName)!);
3361+
return getSourceFile(resolvedLibReferences!.get(libFileName)!.actual);
33213362
}
33223363
}
33233364

@@ -3815,7 +3856,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
38153856

38163857
function pathForLibFile(libFileName: string): string {
38173858
const existing = resolvedLibReferences?.get(libFileName);
3818-
if (existing) return existing;
3859+
if (existing) return existing.actual;
3860+
const result = pathForLibFileWorker(libFileName);
3861+
(resolvedLibReferences ??= new Map()).set(libFileName, result);
3862+
return result.actual;
3863+
}
3864+
3865+
function getLibraryNameAndResolveFrom(libFileName: string) {
38193866
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
38203867
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
38213868
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
@@ -3827,18 +3874,51 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
38273874
i++;
38283875
}
38293876
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);
3877+
const libraryName = "@typescript/lib-" + path;
3878+
return { resolveFrom, libraryName };
3879+
}
3880+
3881+
function pathForLibFileWorker(libFileName: string): LibResolution {
3882+
const existing = resolvedLibProcessing?.get(libFileName);
3883+
if (existing) return existing;
3884+
3885+
if (structureIsReused !== StructureIsReused.Not && oldProgram && !hasInvalidatedLibResolutions(libFileName)) {
3886+
const oldResolution = oldProgram.resolvedLibReferences?.get(libFileName);
3887+
if (oldResolution) {
3888+
if (oldResolution.resolution && isTraceEnabled(options, host)) {
3889+
const { libraryName, resolveFrom } = getLibraryNameAndResolveFrom(libFileName);
3890+
trace(host,
3891+
oldResolution.resolution.resolvedModule ?
3892+
oldResolution.resolution.resolvedModule.packageId ?
3893+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
3894+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
3895+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
3896+
libraryName,
3897+
getNormalizedAbsolutePath(resolveFrom, currentDirectory),
3898+
oldResolution.resolution.resolvedModule?.resolvedFileName,
3899+
oldResolution.resolution.resolvedModule?.packageId && packageIdToString(oldResolution.resolution.resolvedModule.packageId)
3900+
);
3901+
}
3902+
(resolvedLibProcessing ??= new Map()).set(libFileName, oldResolution);
3903+
return oldResolution;
3904+
}
3905+
}
3906+
3907+
const { libraryName, resolveFrom } = getLibraryNameAndResolveFrom(libFileName);
3908+
const resolution = actualResolveLibrary(libraryName, resolveFrom, options, libFileName);
3909+
const result: LibResolution = {
3910+
resolution,
3911+
actual: resolution.resolvedModule ?
3912+
resolution.resolvedModule.resolvedFileName :
3913+
combinePaths(defaultLibraryPath, libFileName)
3914+
};
3915+
(resolvedLibProcessing ??= new Map()).set(libFileName, result);
38353916
return result;
38363917
}
38373918

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

0 commit comments

Comments
 (0)