Skip to content

Commit

Permalink
Add resolveLibrary method on hosts so that library resolution can be …
Browse files Browse the repository at this point in the history
…reused

Fixes #52759, #52707
  • Loading branch information
sheetalkamat committed Apr 20, 2023
1 parent c5dc1a0 commit d171bb6
Show file tree
Hide file tree
Showing 22 changed files with 3,245 additions and 2,187 deletions.
14 changes: 12 additions & 2 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,13 +1249,14 @@ function createModuleOrTypeReferenceResolutionCache<T>(
export function createModuleResolutionCache(
currentDirectory: string,
getCanonicalFileName: (s: string) => string,
options?: CompilerOptions
options?: CompilerOptions,
packageJsonInfoCache?: PackageJsonInfoCache,
): ModuleResolutionCache {
const result = createModuleOrTypeReferenceResolutionCache(
currentDirectory,
getCanonicalFileName,
options,
/*packageJsonInfoCache*/ undefined,
packageJsonInfoCache,
getOriginalOrResolvedModuleFileName,
) as ModuleResolutionCache;
result.getOrCreateCacheForModuleName = (nonRelativeName, mode, redirectedReference) => result.getOrCreateCacheForNonRelativeName(nonRelativeName, mode, redirectedReference);
Expand All @@ -1277,6 +1278,15 @@ export function createTypeReferenceDirectiveResolutionCache(
);
}

/** @internal */
export function getOptionsForLibraryResolution(options: CompilerOptions) {
return { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution };
}

export function resolveLibrary(libraryName: string, resolveFrom: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
return resolveModuleName(libraryName, resolveFrom, getOptionsForLibraryResolution(compilerOptions), host, cache);
}

export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined {
const containingDirectory = getDirectoryPath(containingFile);
return cache.getFromDirectoryCache(moduleName, mode, containingDirectory, /*redirectedReference*/ undefined);
Expand Down
130 changes: 103 additions & 27 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ import {
HasChangedAutomaticTypeDirectiveNames,
hasChangesInResolutions,
hasExtension,
HasInvalidatedLibResolutions,
HasInvalidatedResolutions,
hasJSDocNodes,
hasJSFileExtension,
Expand Down Expand Up @@ -211,6 +212,7 @@ import {
JsxEmit,
length,
libMap,
LibResolution,
libs,
mapDefined,
mapDefinedIterator,
Expand Down Expand Up @@ -276,6 +278,7 @@ import {
ResolvedModuleWithFailedLookupLocations,
ResolvedProjectReference,
ResolvedTypeReferenceDirectiveWithFailedLookupLocations,
resolveLibrary,
resolveModuleName,
resolveTypeReferenceDirective,
returnFalse,
Expand Down Expand Up @@ -1097,6 +1100,32 @@ function forEachProjectReference<T>(
/** @internal */
export const inferredTypesContainingFile = "__inferred type names__.ts";

/** @internal */
export function getInferredLibraryNameResolveFrom(options: CompilerOptions, currentDirectory: string, libFileName: string) {
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
return combinePaths(containingDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
}

function getLibraryNameFromLibFileName(libFileName: string) {
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
const components = libFileName.split(".");
let path = components[1];
let i = 2;
while (components[i] && components[i] !== "d") {
path += (i === 2 ? "/" : "-") + components[i];
i++;
}
return "@typescript/lib-" + path;
}

function getLibFileNameFromLibReference(libReference: FileReference) {
const libName = toFileNameLowerCase(libReference.fileName);
const libFileName = libMap.get(libName);
return { libName, libFileName };
}

interface DiagnosticCache<T extends Diagnostic> {
perFile?: Map<Path, readonly T[]>;
allDiagnostics?: readonly T[];
Expand Down Expand Up @@ -1176,6 +1205,7 @@ export function isProgramUptoDate(
getSourceVersion: (path: Path, fileName: string) => string | undefined,
fileExists: (fileName: string) => boolean,
hasInvalidatedResolutions: HasInvalidatedResolutions,
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
projectReferences: readonly ProjectReference[] | undefined
Expand All @@ -1201,6 +1231,8 @@ export function isProgramUptoDate(
// If the compilation settings do no match, then the program is not up-to-date
if (!compareDataObjects(currentOptions, newOptions)) return false;

if (some(newOptions.lib, hasInvalidatedLibResolutions)) return false;

// If everything matches but the text of config file is changed,
// error locations can change for program options, so update the program
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
Expand All @@ -1209,7 +1241,11 @@ export function isProgramUptoDate(

function sourceFileNotUptoDate(sourceFile: SourceFile) {
return !sourceFileVersionUptoDate(sourceFile) ||
hasInvalidatedResolutions(sourceFile.path);
hasInvalidatedResolutions(sourceFile.path) ||
some(sourceFile.libReferenceDirectives, libRef => {
const { libFileName } = getLibFileNameFromLibReference(libRef);
return !!libFileName && hasInvalidatedLibResolutions(libFileName);
});
}

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

let resolvedLibReferences: Map<string, string> | undefined;
let resolvedLibReferences: Map<string, LibResolution> | undefined;
let resolvedLibProcessing: Map<string, LibResolution> | undefined;

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

const hasInvalidatedLibResolutions = host.hasInvalidatedLibResolutions || returnFalse;
let actualResolveLibrary: (libraryName: string, resolveFrom: string, options: CompilerOptions, libFileName: string) => ResolvedModuleWithFailedLookupLocations;
if (host.resolveLibrary) {
actualResolveLibrary = host.resolveLibrary.bind(host);
}
else {
const libraryResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options, moduleResolutionCache?.getPackageJsonInfoCache());
actualResolveLibrary = (libraryName, resolveFrom, options) =>
resolveLibrary(libraryName, resolveFrom, options, host, libraryResolutionCache);
}

// Map from a stringified PackageId to the source file with that id.
// Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile).
// `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around.
Expand Down Expand Up @@ -1774,6 +1822,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg

// unconditionally set oldProgram to undefined to prevent it from being captured in closure
oldProgram = undefined;
resolvedLibProcessing = undefined;

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

if (oldProgram.resolvedLibReferences &&
forEachEntry(oldProgram.resolvedLibReferences, (resolution, libFileName) => pathForLibFileWorker(libFileName).actual !== resolution.actual)) {
return StructureIsReused.SafeModules;
}

if (host.hasChangedAutomaticTypeDirectiveNames) {
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
}
Expand Down Expand Up @@ -2600,7 +2654,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return equalityComparer(file.fileName, getDefaultLibraryFileName());
}
else {
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!));
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
}
}

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

function getLibFileFromReference(ref: FileReference) {
const libName = toFileNameLowerCase(ref.fileName);
const libFileName = libMap.get(libName);
if (libFileName) {
return getSourceFile(resolvedLibReferences?.get(libFileName)!);
}
const { libFileName } = getLibFileNameFromLibReference(ref);
const actualFileName = libFileName && resolvedLibReferences?.get(libFileName)?.actual;
return actualFileName !== undefined ? getSourceFile(actualFileName) : undefined;
}

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

function pathForLibFile(libFileName: string): string {
const existing = resolvedLibReferences?.get(libFileName);
if (existing) return existing;
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
const components = libFileName.split(".");
let path = components[1];
let i = 2;
while (components[i] && components[i] !== "d") {
path += (i === 2 ? "/" : "-") + components[i];
i++;
}
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
const resolveFrom = combinePaths(containingDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution }, host, moduleResolutionCache);
const result = localOverrideModuleResult?.resolvedModule ?
localOverrideModuleResult.resolvedModule.resolvedFileName :
combinePaths(defaultLibraryPath, libFileName);
if (existing) return existing.actual;
const result = pathForLibFileWorker(libFileName);
(resolvedLibReferences ??= new Map()).set(libFileName, result);
return result.actual;
}

function pathForLibFileWorker(libFileName: string): LibResolution {
const existing = resolvedLibProcessing?.get(libFileName);
if (existing) return existing;

if (structureIsReused !== StructureIsReused.Not && oldProgram && !hasInvalidatedLibResolutions(libFileName)) {
const oldResolution = oldProgram.resolvedLibReferences?.get(libFileName);
if (oldResolution) {
if (oldResolution.resolution && isTraceEnabled(options, host)) {
const libraryName = getLibraryNameFromLibFileName(libFileName);
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
trace(host,
oldResolution.resolution.resolvedModule ?
oldResolution.resolution.resolvedModule.packageId ?
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
libraryName,
getNormalizedAbsolutePath(resolveFrom, currentDirectory),
oldResolution.resolution.resolvedModule?.resolvedFileName,
oldResolution.resolution.resolvedModule?.packageId && packageIdToString(oldResolution.resolution.resolvedModule.packageId)
);
}
(resolvedLibProcessing ??= new Map()).set(libFileName, oldResolution);
return oldResolution;
}
}

const libraryName = getLibraryNameFromLibFileName(libFileName);
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
const resolution = actualResolveLibrary(libraryName, resolveFrom, options, libFileName);
const result: LibResolution = {
resolution,
actual: resolution.resolvedModule ?
resolution.resolvedModule.resolvedFileName :
combinePaths(defaultLibraryPath, libFileName)
};
(resolvedLibProcessing ??= new Map()).set(libFileName, result);
return result;
}

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

0 comments on commit d171bb6

Please sign in to comment.