Skip to content

Commit ddd5084

Browse files
authored
Add resolveLibrary method on hosts and store resolvedLibraries in program so that resolutions can be reused (#53877)
1 parent e6543e1 commit ddd5084

File tree

49 files changed

+15328
-481
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+15328
-481
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 13 additions & 2 deletions
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,16 @@ 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+
/** @internal */
1287+
export function resolveLibrary(libraryName: string, resolveFrom: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
1288+
return resolveModuleName(libraryName, resolveFrom, getOptionsForLibraryResolution(compilerOptions), host, cache);
1289+
}
1290+
12801291
export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined {
12811292
const containingDirectory = getDirectoryPath(containingFile);
12821293
return cache.getFromDirectoryCache(moduleName, mode, containingDirectory, /*redirectedReference*/ undefined);

src/compiler/program.ts

Lines changed: 109 additions & 24 deletions
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,9 @@ 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 library resolution is invalidated, then the program is not up-to-date
1235+
if (program.resolvedLibReferences && forEachEntry(program.resolvedLibReferences, (_value, libFileName) => hasInvalidatedLibResolutions(libFileName))) return false;
1236+
12041237
// If everything matches but the text of config file is changed,
12051238
// error locations can change for program options, so update the program
12061239
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
@@ -1469,6 +1502,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
14691502
let automaticTypeDirectiveNames: string[] | undefined;
14701503
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
14711504

1505+
let resolvedLibReferences: Map<string, LibResolution> | undefined;
1506+
let resolvedLibProcessing: Map<string, LibResolution> | undefined;
1507+
14721508
// The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules.
14731509
// This works as imported modules are discovered recursively in a depth first manner, specifically:
14741510
// - For each root file, findSourceFile is called.
@@ -1592,6 +1628,17 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
15921628
);
15931629
}
15941630

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

17731820
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
17741821
oldProgram = undefined;
1822+
resolvedLibProcessing = undefined;
17751823

17761824
const program: Program = {
17771825
getRootFileNames: () => rootNames,
@@ -1813,6 +1861,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
18131861
sourceFileToPackageName,
18141862
redirectTargetsMap,
18151863
usesUriStyleNodeCoreModules,
1864+
resolvedLibReferences,
18161865
isEmittedFile,
18171866
getConfigFileParsingDiagnostics,
18181867
getProjectReferences,
@@ -2441,6 +2490,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24412490
return StructureIsReused.SafeModules;
24422491
}
24432492

2493+
if (oldProgram.resolvedLibReferences &&
2494+
forEachEntry(oldProgram.resolvedLibReferences, (resolution, libFileName) => pathForLibFileWorker(libFileName).actual !== resolution.actual)) {
2495+
return StructureIsReused.SafeModules;
2496+
}
2497+
24442498
if (host.hasChangedAutomaticTypeDirectiveNames) {
24452499
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
24462500
}
@@ -2481,6 +2535,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
24812535
sourceFileToPackageName = oldProgram.sourceFileToPackageName;
24822536
redirectTargetsMap = oldProgram.redirectTargetsMap;
24832537
usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules;
2538+
resolvedLibReferences = oldProgram.resolvedLibReferences;
24842539

24852540
return StructureIsReused.Completely;
24862541
}
@@ -2596,7 +2651,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
25962651
return equalityComparer(file.fileName, getDefaultLibraryFileName());
25972652
}
25982653
else {
2599-
return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName)));
2654+
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
26002655
}
26012656
}
26022657

@@ -3310,11 +3365,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
33103365
}
33113366

33123367
function getLibFileFromReference(ref: FileReference) {
3313-
const libName = toFileNameLowerCase(ref.fileName);
3314-
const libFileName = libMap.get(libName);
3315-
if (libFileName) {
3316-
return getSourceFile(pathForLibFile(libFileName));
3317-
}
3368+
const { libFileName } = getLibFileNameFromLibReference(ref);
3369+
const actualFileName = libFileName && resolvedLibReferences?.get(libFileName)?.actual;
3370+
return actualFileName !== undefined ? getSourceFile(actualFileName) : undefined;
33183371
}
33193372

33203373
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
@@ -3810,29 +3863,61 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
38103863
}
38113864

38123865
function pathForLibFile(libFileName: string): string {
3813-
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
3814-
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
3815-
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
3816-
const components = libFileName.split(".");
3817-
let path = components[1];
3818-
let i = 2;
3819-
while (components[i] && components[i] !== "d") {
3820-
path += (i === 2 ? "/" : "-") + components[i];
3821-
i++;
3822-
}
3823-
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
3824-
const resolveFrom = combinePaths(containingDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
3825-
const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution }, host, moduleResolutionCache);
3826-
if (localOverrideModuleResult?.resolvedModule) {
3827-
return localOverrideModuleResult.resolvedModule.resolvedFileName;
3866+
const existing = resolvedLibReferences?.get(libFileName);
3867+
if (existing) return existing.actual;
3868+
const result = pathForLibFileWorker(libFileName);
3869+
(resolvedLibReferences ??= new Map()).set(libFileName, result);
3870+
return result.actual;
3871+
}
3872+
3873+
function pathForLibFileWorker(libFileName: string): LibResolution {
3874+
const existing = resolvedLibProcessing?.get(libFileName);
3875+
if (existing) return existing;
3876+
3877+
if (structureIsReused !== StructureIsReused.Not && oldProgram && !hasInvalidatedLibResolutions(libFileName)) {
3878+
const oldResolution = oldProgram.resolvedLibReferences?.get(libFileName);
3879+
if (oldResolution) {
3880+
if (oldResolution.resolution && isTraceEnabled(options, host)) {
3881+
const libraryName = getLibraryNameFromLibFileName(libFileName);
3882+
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
3883+
trace(host,
3884+
oldResolution.resolution.resolvedModule ?
3885+
oldResolution.resolution.resolvedModule.packageId ?
3886+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
3887+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
3888+
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
3889+
libraryName,
3890+
getNormalizedAbsolutePath(resolveFrom, currentDirectory),
3891+
oldResolution.resolution.resolvedModule?.resolvedFileName,
3892+
oldResolution.resolution.resolvedModule?.packageId && packageIdToString(oldResolution.resolution.resolvedModule.packageId)
3893+
);
3894+
}
3895+
(resolvedLibProcessing ??= new Map()).set(libFileName, oldResolution);
3896+
return oldResolution;
3897+
}
38283898
}
3829-
return combinePaths(defaultLibraryPath, libFileName);
3899+
3900+
const libraryName = getLibraryNameFromLibFileName(libFileName);
3901+
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
3902+
tracing?.push(tracing.Phase.Program, "resolveLibrary", { resolveFrom });
3903+
performance.mark("beforeResolveLibrary");
3904+
const resolution = actualResolveLibrary(libraryName, resolveFrom, options, libFileName);
3905+
performance.mark("afterResolveLibrary");
3906+
performance.measure("ResolveLibrary", "beforeResolveLibrary", "afterResolveLibrary");
3907+
tracing?.pop();
3908+
const result: LibResolution = {
3909+
resolution,
3910+
actual: resolution.resolvedModule ?
3911+
resolution.resolvedModule.resolvedFileName :
3912+
combinePaths(defaultLibraryPath, libFileName)
3913+
};
3914+
(resolvedLibProcessing ??= new Map()).set(libFileName, result);
3915+
return result;
38303916
}
38313917

38323918
function processLibReferenceDirectives(file: SourceFile) {
38333919
forEach(file.libReferenceDirectives, (libReference, index) => {
3834-
const libName = toFileNameLowerCase(libReference.fileName);
3835-
const libFileName = libMap.get(libName);
3920+
const { libName, libFileName } = getLibFileNameFromLibReference(libReference);
38363921
if (libFileName) {
38373922
// we ignore any 'no-default-lib' reference set on this file.
38383923
processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, });

0 commit comments

Comments
 (0)