Skip to content

Commit c54cfef

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

22 files changed

+1334
-963
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

+102-18
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,12 @@ function forEachProjectReference<T>(
10971106
/** @internal */
10981107
export const inferredTypesContainingFile = "__inferred type names__.ts";
10991108

1109+
/** @internal */
1110+
export function getInferredLibrarayNameResolveFrom(options: CompilerOptions, currentDirectory: string, libFileName: string) {
1111+
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
1112+
return combinePaths(containingDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
1113+
}
1114+
11001115
interface DiagnosticCache<T extends Diagnostic> {
11011116
perFile?: Map<Path, readonly T[]>;
11021117
allDiagnostics?: readonly T[];
@@ -1176,6 +1191,7 @@ export function isProgramUptoDate(
11761191
getSourceVersion: (path: Path, fileName: string) => string | undefined,
11771192
fileExists: (fileName: string) => boolean,
11781193
hasInvalidatedResolutions: HasInvalidatedResolutions,
1194+
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
11791195
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
11801196
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
11811197
projectReferences: readonly ProjectReference[] | undefined
@@ -1201,6 +1217,8 @@ export function isProgramUptoDate(
12011217
// If the compilation settings do no match, then the program is not up-to-date
12021218
if (!compareDataObjects(currentOptions, newOptions)) return false;
12031219

1220+
if (some(newOptions.lib, hasInvalidatedLibResolutions)) return false;
1221+
12041222
// If everything matches but the text of config file is changed,
12051223
// error locations can change for program options, so update the program
12061224
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
@@ -1209,7 +1227,11 @@ export function isProgramUptoDate(
12091227

12101228
function sourceFileNotUptoDate(sourceFile: SourceFile) {
12111229
return !sourceFileVersionUptoDate(sourceFile) ||
1212-
hasInvalidatedResolutions(sourceFile.path);
1230+
hasInvalidatedResolutions(sourceFile.path) ||
1231+
some(sourceFile.libReferenceDirectives, libRef => {
1232+
const { libFileName } = getLibFileName(libRef);
1233+
return !!libFileName && hasInvalidatedLibResolutions(libFileName);
1234+
});
12131235
}
12141236

12151237
function sourceFileVersionUptoDate(sourceFile: SourceFile) {
@@ -1469,7 +1491,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
14691491
let automaticTypeDirectiveNames: string[] | undefined;
14701492
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
14711493

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

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

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

17751809
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
17761810
oldProgram = undefined;
1811+
resolvedLibProcessing = undefined;
17771812

17781813
const program: Program = {
17791814
getRootFileNames: () => rootNames,
@@ -2434,6 +2469,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24342469
else {
24352470
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
24362471
}
2472+
// Do this resolution if necessary to determine reconstruction of program
2473+
if (structureIsReused !== StructureIsReused.Completely &&
2474+
some(newSourceFile.libReferenceDirectives, libReference => {
2475+
const { libFileName } = getLibFileName(libReference);
2476+
return !!libFileName &&
2477+
pathForLibFileWorker(libFileName).actual !== oldProgram?.resolvedLibReferences?.get(libFileName)?.actual;
2478+
})) {
2479+
structureIsReused = StructureIsReused.SafeModules;
2480+
}
24372481
}
24382482

24392483
if (structureIsReused !== StructureIsReused.Completely) {
@@ -2444,6 +2488,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24442488
return StructureIsReused.SafeModules;
24452489
}
24462490

2491+
if (some(options.lib, libFileName => pathForLibFileWorker(libFileName).actual !== oldProgram?.resolvedLibReferences?.get(libFileName)?.actual)) {
2492+
return StructureIsReused.SafeModules;
2493+
}
2494+
24472495
if (host.hasChangedAutomaticTypeDirectiveNames) {
24482496
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
24492497
}
@@ -2600,7 +2648,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26002648
return equalityComparer(file.fileName, getDefaultLibraryFileName());
26012649
}
26022650
else {
2603-
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!));
2651+
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
26042652
}
26052653
}
26062654

@@ -3314,11 +3362,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
33143362
}
33153363

33163364
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-
}
3365+
const { libFileName } = getLibFileName(ref);
3366+
const actualFileName = libFileName && resolvedLibReferences?.get(libFileName)?.actual;
3367+
return actualFileName !== undefined ? getSourceFile(actualFileName) : undefined;
33223368
}
33233369

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

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

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

0 commit comments

Comments
 (0)