Skip to content

Commit fc8c4da

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

23 files changed

+3266
-2188
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

+103-27
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,
@@ -1097,6 +1100,32 @@ function forEachProjectReference<T>(
10971100
/** @internal */
10981101
export const inferredTypesContainingFile = "__inferred type names__.ts";
10991102

1103+
/** @internal */
1104+
export function getInferredLibraryNameResolveFrom(options: CompilerOptions, currentDirectory: string, libFileName: string) {
1105+
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
1106+
return combinePaths(containingDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
1107+
}
1108+
1109+
function getLibraryNameFromLibFileName(libFileName: string) {
1110+
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
1111+
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
1112+
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
1113+
const components = libFileName.split(".");
1114+
let path = components[1];
1115+
let i = 2;
1116+
while (components[i] && components[i] !== "d") {
1117+
path += (i === 2 ? "/" : "-") + components[i];
1118+
i++;
1119+
}
1120+
return "@typescript/lib-" + path;
1121+
}
1122+
1123+
function getLibFileNameFromLibReference(libReference: FileReference) {
1124+
const libName = toFileNameLowerCase(libReference.fileName);
1125+
const libFileName = libMap.get(libName);
1126+
return { libName, libFileName };
1127+
}
1128+
11001129
interface DiagnosticCache<T extends Diagnostic> {
11011130
perFile?: Map<Path, readonly T[]>;
11021131
allDiagnostics?: readonly T[];
@@ -1176,6 +1205,7 @@ export function isProgramUptoDate(
11761205
getSourceVersion: (path: Path, fileName: string) => string | undefined,
11771206
fileExists: (fileName: string) => boolean,
11781207
hasInvalidatedResolutions: HasInvalidatedResolutions,
1208+
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
11791209
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
11801210
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
11811211
projectReferences: readonly ProjectReference[] | undefined
@@ -1201,6 +1231,8 @@ export function isProgramUptoDate(
12011231
// If the compilation settings do no match, then the program is not up-to-date
12021232
if (!compareDataObjects(currentOptions, newOptions)) return false;
12031233

1234+
if (some(newOptions.lib, hasInvalidatedLibResolutions)) return false;
1235+
12041236
// If everything matches but the text of config file is changed,
12051237
// error locations can change for program options, so update the program
12061238
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
@@ -1209,7 +1241,11 @@ export function isProgramUptoDate(
12091241

12101242
function sourceFileNotUptoDate(sourceFile: SourceFile) {
12111243
return !sourceFileVersionUptoDate(sourceFile) ||
1212-
hasInvalidatedResolutions(sourceFile.path);
1244+
hasInvalidatedResolutions(sourceFile.path) ||
1245+
some(sourceFile.libReferenceDirectives, libRef => {
1246+
const { libFileName } = getLibFileNameFromLibReference(libRef);
1247+
return !!libFileName && hasInvalidatedLibResolutions(libFileName);
1248+
});
12131249
}
12141250

12151251
function sourceFileVersionUptoDate(sourceFile: SourceFile) {
@@ -1469,7 +1505,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
14691505
let automaticTypeDirectiveNames: string[] | undefined;
14701506
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
14711507

1472-
let resolvedLibReferences: Map<string, string> | undefined;
1508+
let resolvedLibReferences: Map<string, LibResolution> | undefined;
1509+
let resolvedLibProcessing: Map<string, LibResolution> | undefined;
14731510

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

1634+
const hasInvalidatedLibResolutions = host.hasInvalidatedLibResolutions || returnFalse;
1635+
let actualResolveLibrary: (libraryName: string, resolveFrom: string, options: CompilerOptions, libFileName: string) => ResolvedModuleWithFailedLookupLocations;
1636+
if (host.resolveLibrary) {
1637+
actualResolveLibrary = host.resolveLibrary.bind(host);
1638+
}
1639+
else {
1640+
const libraryResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options, moduleResolutionCache?.getPackageJsonInfoCache());
1641+
actualResolveLibrary = (libraryName, resolveFrom, options) =>
1642+
resolveLibrary(libraryName, resolveFrom, options, host, libraryResolutionCache);
1643+
}
1644+
15971645
// Map from a stringified PackageId to the source file with that id.
15981646
// Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile).
15991647
// `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around.
@@ -1774,6 +1822,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
17741822

17751823
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
17761824
oldProgram = undefined;
1825+
resolvedLibProcessing = undefined;
17771826

17781827
const program: Program = {
17791828
getRootFileNames: () => rootNames,
@@ -2444,6 +2493,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24442493
return StructureIsReused.SafeModules;
24452494
}
24462495

2496+
if (oldProgram.resolvedLibReferences &&
2497+
forEachEntry(oldProgram.resolvedLibReferences, (resolution, libFileName) => pathForLibFileWorker(libFileName).actual !== resolution.actual)) {
2498+
return StructureIsReused.SafeModules;
2499+
}
2500+
24472501
if (host.hasChangedAutomaticTypeDirectiveNames) {
24482502
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
24492503
}
@@ -2600,7 +2654,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
26002654
return equalityComparer(file.fileName, getDefaultLibraryFileName());
26012655
}
26022656
else {
2603-
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!));
2657+
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
26042658
}
26052659
}
26062660

@@ -3314,11 +3368,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
33143368
}
33153369

33163370
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-
}
3371+
const { libFileName } = getLibFileNameFromLibReference(ref);
3372+
const actualFileName = libFileName && resolvedLibReferences?.get(libFileName)?.actual;
3373+
return actualFileName !== undefined ? getSourceFile(actualFileName) : undefined;
33223374
}
33233375

33243376
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
@@ -3815,31 +3867,55 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
38153867

38163868
function pathForLibFile(libFileName: string): string {
38173869
const existing = resolvedLibReferences?.get(libFileName);
3818-
if (existing) return existing;
3819-
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
3820-
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
3821-
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
3822-
const components = libFileName.split(".");
3823-
let path = components[1];
3824-
let i = 2;
3825-
while (components[i] && components[i] !== "d") {
3826-
path += (i === 2 ? "/" : "-") + components[i];
3827-
i++;
3828-
}
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);
3870+
if (existing) return existing.actual;
3871+
const result = pathForLibFileWorker(libFileName);
38353872
(resolvedLibReferences ??= new Map()).set(libFileName, result);
3873+
return result.actual;
3874+
}
3875+
3876+
function pathForLibFileWorker(libFileName: string): LibResolution {
3877+
const existing = resolvedLibProcessing?.get(libFileName);
3878+
if (existing) return existing;
3879+
3880+
if (structureIsReused !== StructureIsReused.Not && oldProgram && !hasInvalidatedLibResolutions(libFileName)) {
3881+
const oldResolution = oldProgram.resolvedLibReferences?.get(libFileName);
3882+
if (oldResolution) {
3883+
if (oldResolution.resolution && isTraceEnabled(options, host)) {
3884+
const libraryName = getLibraryNameFromLibFileName(libFileName);
3885+
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
3886+
trace(host,
3887+
oldResolution.resolution.resolvedModule ?
3888+
oldResolution.resolution.resolvedModule.packageId ?
3889+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
3890+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
3891+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
3892+
libraryName,
3893+
getNormalizedAbsolutePath(resolveFrom, currentDirectory),
3894+
oldResolution.resolution.resolvedModule?.resolvedFileName,
3895+
oldResolution.resolution.resolvedModule?.packageId && packageIdToString(oldResolution.resolution.resolvedModule.packageId)
3896+
);
3897+
}
3898+
(resolvedLibProcessing ??= new Map()).set(libFileName, oldResolution);
3899+
return oldResolution;
3900+
}
3901+
}
3902+
3903+
const libraryName = getLibraryNameFromLibFileName(libFileName);
3904+
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
3905+
const resolution = actualResolveLibrary(libraryName, resolveFrom, options, libFileName);
3906+
const result: LibResolution = {
3907+
resolution,
3908+
actual: resolution.resolvedModule ?
3909+
resolution.resolvedModule.resolvedFileName :
3910+
combinePaths(defaultLibraryPath, libFileName)
3911+
};
3912+
(resolvedLibProcessing ??= new Map()).set(libFileName, result);
38363913
return result;
38373914
}
38383915

38393916
function processLibReferenceDirectives(file: SourceFile) {
38403917
forEach(file.libReferenceDirectives, (libReference, index) => {
3841-
const libName = toFileNameLowerCase(libReference.fileName);
3842-
const libFileName = libMap.get(libName);
3918+
const { libName, libFileName } = getLibFileNameFromLibReference(libReference);
38433919
if (libFileName) {
38443920
// we ignore any 'no-default-lib' reference set on this file.
38453921
processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, });

0 commit comments

Comments
 (0)