Skip to content

Commit

Permalink
Cache packagejson scopes per directory
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat committed Dec 9, 2022
1 parent b2159e4 commit 7bb44e7
Show file tree
Hide file tree
Showing 46 changed files with 5,120 additions and 11,443 deletions.
68 changes: 51 additions & 17 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
getOriginalOrResolvedModuleFileName,
getOriginalOrResolvedTypeReferenceFileName,
getOwnKeys,
getPackageJsonLocationFromScope,
getRelativePathFromDirectory,
getTsBuildInfoEmitOutputFilePath,
handleNoEmitOptions,
Expand All @@ -66,7 +67,6 @@ import {
isJsonSourceFile,
isNumber,
isString,
last,
map,
mapDefined,
maybeBind,
Expand All @@ -80,6 +80,7 @@ import {
outFile,
PackageJsonInfo,
PackageJsonInfoCache,
PackageJsonScope,
Path,
PerDirectoryAndNonRelativeNameCache,
PerNonRelativeNameCache,
Expand Down Expand Up @@ -187,7 +188,7 @@ export interface ReusableBuilderProgramState extends BuilderState {
modules: PerDirectoryAndNonRelativeNameCache<ResolvedModuleWithFailedLookupLocations> | undefined;
typeRefs: PerDirectoryAndNonRelativeNameCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations> | undefined;
packageJsons: Map<Path, string> | undefined;
nonRelativePackageJsonsCache: PerNonRelativeNameCache<string> | undefined;
packageJsonScopes: PerNonRelativeNameCache<PackageJsonScope> | undefined;
packageJsonCache: PackageJsonInfoCache | undefined;
};
resuableCacheResolutions?: {
Expand Down Expand Up @@ -1472,20 +1473,19 @@ function getCacheResolutions(state: BuilderProgramState) {
let modules: PerDirectoryAndNonRelativeNameCache<ResolvedModuleWithFailedLookupLocations> | undefined;
let typeRefs: PerDirectoryAndNonRelativeNameCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations> | undefined;
let packageJsons: Map<Path, string> | undefined;
let nonRelativePackageJsonsCache: PerNonRelativeNameCache<string> | undefined;
let packageJsonScopes: PerNonRelativeNameCache<PackageJsonScope> | undefined;
for (const f of state.program!.getSourceFiles()) {
modules = toPerDirectoryAndNonRelativeNameCache(state, modules, getOriginalOrResolvedModuleFileName, f.resolvedModules, f);
typeRefs = toPerDirectoryAndNonRelativeNameCache(state, typeRefs, getOriginalOrResolvedTypeReferenceFileName, f.resolvedTypeReferenceDirectiveNames, f);
if (f.packageJsonScope) {
if (f.packageJsonScope?.info) {
const dirPath = getDirectoryPath(f.resolvedPath);
if (!nonRelativePackageJsonsCache?.getWithPath(dirPath)) {
const result = last(f.packageJsonLocations!);
(packageJsons ??= new Map()).set(dirPath, result);
(nonRelativePackageJsonsCache ??= createPerNonRelativeNameCache(
if (!packageJsonScopes?.getWithPath(dirPath)) {
(packageJsons ??= new Map()).set(dirPath, getPackageJsonLocationFromScope(f.packageJsonScope));
(packageJsonScopes ??= createPerNonRelativeNameCache(
state.program!.getCurrentDirectory(),
state.program!.getCanonicalFileName,
identity,
)).setWithPath(dirPath, result, ancestorPath => packageJsons!.delete(ancestorPath));
getPackageJsonLocationFromScope,
)).setWithPath(dirPath, f.packageJsonScope, ancestorPath => packageJsons!.delete(ancestorPath));
}
}
}
Expand All @@ -1498,7 +1498,7 @@ function getCacheResolutions(state: BuilderProgramState) {
modules,
typeRefs,
packageJsons,
nonRelativePackageJsonsCache,
packageJsonScopes,
packageJsonCache: state.program!.getModuleResolutionCache()?.getPackageJsonInfoCache().clone(),
};
}
Expand Down Expand Up @@ -2158,6 +2158,7 @@ export function createOldBuildInfoProgram(
if (!cacheResolutions && !resuableCacheResolutions) return undefined;
const fileExistsMap = new Map<string, boolean>();
const affectingLoationsSameMap = new Map<string, boolean>();
const packageJsonInfoMap = new Map<string, PackageJsonInfo | false>();

type Resolution = ResolvedModuleWithFailedLookupLocations & ResolvedTypeReferenceDirectiveWithFailedLookupLocations;
type ResolutionEntry = [name: string, resolutionId: ProgramBuildInfoResolutionId, mode: ResolutionMode];
Expand All @@ -2168,6 +2169,7 @@ export function createOldBuildInfoProgram(
const reusableResolvedModules = intializeReusableResolutionsCache(resuableCacheResolutions?.cache.modules);
const reusableResolvedTypeRefs = intializeReusableResolutionsCache(resuableCacheResolutions?.cache.typeRefs);
let decodedPackageJsons: PerNonRelativeNameCache<string> | undefined;
let packageJsonScopes: Map<string, PackageJsonScope | false> | undefined;
let decodedHashes: Map<ProgramBuildInfoAbsoluteFileId, string | undefined> | undefined;
let resolutions: (Resolution | false)[] | undefined;
let originalPathOrResolvedFileNames: string[] | undefined;
Expand All @@ -2194,7 +2196,7 @@ export function createOldBuildInfoProgram(
dirPath,
redirectedReference,
),
getPackageJsonPath,
getPackageJsonScope,
};

function intializeReusableResolutionsCache(reusable: ProgramBuildInfoResolutionCacheWithRedirects | undefined): ReusableResolutionsCache | undefined {
Expand Down Expand Up @@ -2226,10 +2228,28 @@ export function createOldBuildInfoProgram(
return result;
}

function getPackageJsonPath(dir: string) {
const fromCache = cacheResolutions?.nonRelativePackageJsonsCache?.get(dir);
function getPackageJsonInfo(fileName: string) {
let result = packageJsonInfoMap.get(fileName);
if (result === undefined) packageJsonInfoMap.set(fileName, result = host.getPackageJsonInfo(fileName) || false);
return result || undefined;
}

function getPackageJsonScope(dir: string): PackageJsonScope | undefined{
const fromCache = cacheResolutions?.packageJsonScopes?.get(dir);
if (fromCache) {
return fileExists(fromCache) ? fromCache : undefined;
const packageJson = getPackageJsonLocationFromScope(fromCache)!;
let result = packageJsonScopes?.get(packageJson);
if (result === undefined) {
(packageJsonScopes ??= new Map()).set(
packageJson,
result = affectingLocationsSame(packageJson, fromCache.info) ?
fromCache :
fileExists(packageJson) ?
{ info: getPackageJsonInfo(packageJson), affectingLocations: [packageJson] } :
false
);
}
return result || undefined;
}
if (!resuableCacheResolutions?.cache.packageJsons) return;
if (!decodedPackageJsons) {
Expand All @@ -2252,8 +2272,22 @@ export function createOldBuildInfoProgram(
decodedPackageJsons.setWithPath(dirPath, packageJson, noop);
}
}
const fromDecoded = decodedPackageJsons.get(dir);
return fromDecoded && fileExists(fromDecoded) ? fromDecoded : undefined;
return toPackageJsonScope(decodedPackageJsons.get(dir));
}

function toPackageJsonScope(file: string | undefined): PackageJsonScope | undefined {
if (!file) return undefined;
let result = packageJsonScopes?.get(file);
if (result !== undefined) return result || undefined;
(packageJsonScopes ??= new Map());
if (fileExists(file)) {
result = {
info: getPackageJsonInfo(file),
affectingLocations: [file]
};
}
packageJsonScopes.set(file, result || false);
return result;
}

function getResolvedFromCache<T extends ResolvedModuleWithFailedLookupLocations | ResolvedTypeReferenceDirectiveWithFailedLookupLocations>(
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4728,7 +4728,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let diagnosticDetails;
const ext = tryGetExtensionFromPath(currentSourceFile.fileName);
if (ext === Extension.Ts || ext === Extension.Js || ext === Extension.Tsx || ext === Extension.Jsx) {
const scope = currentSourceFile.packageJsonScope;
const scope = currentSourceFile.packageJsonScope?.info;
const targetExt = ext === Extension.Ts ? Extension.Mts : ext === Extension.Js ? Extension.Mjs : undefined;
if (scope && !scope.contents.packageJsonContent.type) {
if (targetExt) {
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5154,6 +5154,14 @@
"category": "Message",
"code": 6261
},
"Directory '{0}' resolves to '{1}' scope according to cache.": {
"category": "Message",
"code": 6263
},
"Directory '{0}' has no containing package.json scope according to cache.": {
"category": "Message",
"code": 6264
},

"Directory '{0}' has no containing package.json scope. Imports will not resolve.": {
"category": "Message",
Expand Down
109 changes: 90 additions & 19 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,6 @@ export function getAutomaticTypeDirectiveNames(options: CompilerOptions, host: M
}

export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>, NonRelativeNameResolutionCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>, PackageJsonInfoCache {
/** @internal */ clearAllExceptPackageJsonInfoCache(): void;
}

export interface ModeAwareCache<T> {
Expand Down Expand Up @@ -744,11 +743,11 @@ export interface PerNonRelativeNameCache<T> {
set(directory: string, result: T): void;
/** @internal */ getWithPath(directory: Path): T | undefined;
/** @internal */ setWithPath(directory: Path, result: T, ancestoryWorker: (directory: Path) => void): void;
/** @internal */ clear(): void;
}

export interface ModuleResolutionCache extends PerDirectoryResolutionCache<ResolvedModuleWithFailedLookupLocations>, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache {
getPackageJsonInfoCache(): PackageJsonInfoCache;
/** @internal */ clearAllExceptPackageJsonInfoCache(): void;
}

/**
Expand All @@ -760,12 +759,32 @@ export interface NonRelativeModuleNameResolutionCache extends NonRelativeNameRes
getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ResolutionMode, redirectedReference?: ResolvedProjectReference): PerModuleNameCache;
}

/** @internal */
export interface PackageJsonScope {
info: PackageJsonInfo | undefined;
failedLookupLocations?: string[];
affectingLocations?: string[];
}
/** @internal */
export function getPackageJsonLocationFromScope(scope: PackageJsonScope) {
return lastOrUndefined(scope.affectingLocations);
}

/** @internal */
export interface OldPackageJsonInfoCache {
getPackageJsonScope(dir: string): PackageJsonScope | undefined;
}

export interface PackageJsonInfoCache {
/** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined;
/** @internal */ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean): void;
/** @internal */ entries(): [Path, PackageJsonInfo | boolean][];
/** @internal */ getInternalMap(): Map<Path, PackageJsonInfo | boolean> | undefined;
/** @internal */ setInternalMap(value: Map<Path, PackageJsonInfo | boolean> | undefined): void;
/** @internal */ clone(): PackageJsonInfoCache;
/** @internal */ getPackageJsonScope(dir: string): PackageJsonScope | undefined;
/** @internal */ setPackageJsonScope(dir: string, scope: PackageJsonScope): void;
/** @internal */ setOldPackageJsonScopeCache(cache: OldPackageJsonInfoCache | undefined): void;
clear(): void;
}

Expand Down Expand Up @@ -886,26 +905,44 @@ function createCacheWithRedirects<K, V>(ownOptions: CompilerOptions | undefined)
}

function createPackageJsonInfoCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, cache?: Map<Path, PackageJsonInfo | boolean>): PackageJsonInfoCache {
return { getPackageJsonInfo, setPackageJsonInfo, clear, entries, getInternalMap, clone };
const packageJsonScopes = createPerNonRelativeNameCache(currentDirectory, getCanonicalFileName, getPackageJsonLocationFromScope);
let oldPackageJsonInfoCache: OldPackageJsonInfoCache | undefined;
return {
getPackageJsonInfo,
setPackageJsonInfo,
clear,
entries,
getInternalMap: () => cache,
setInternalMap: value => cache = value,
clone,
getPackageJsonScope,
setPackageJsonScope,
setOldPackageJsonScopeCache: cache => oldPackageJsonInfoCache = cache,
};
function getPackageJsonInfo(packageJsonPath: string) {
return cache?.get(toPath(packageJsonPath, currentDirectory, getCanonicalFileName));
}
function setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfo | boolean) {
(cache ||= new Map()).set(toPath(packageJsonPath, currentDirectory, getCanonicalFileName), info);
}
function clear() {
oldPackageJsonInfoCache = undefined;
cache = undefined;
packageJsonScopes.clear();
}
function entries() {
const iter = cache?.entries();
return iter ? arrayFrom(iter) : [];
}
function getInternalMap() {
return cache;
}
function clone() {
return createPackageJsonInfoCache(currentDirectory, getCanonicalFileName, cache && new Map(cache));
}
function getPackageJsonScope(dir: string) {
return packageJsonScopes.get(dir) || oldPackageJsonInfoCache?.getPackageJsonScope(dir);
}
function setPackageJsonScope(dir: string, scope: PackageJsonScope) {
packageJsonScopes.set(dir, scope);
}
}

function getOrCreateCache<K, V>(cacheWithRedirects: CacheWithRedirects<K, V>, redirectedReference: ResolvedProjectReference | undefined, key: K, create: () => V): V {
Expand Down Expand Up @@ -1041,7 +1078,11 @@ export function createPerNonRelativeNameCache<T>(
): PerNonRelativeNameCache<T> {
const directoryPathMap = new Map<Path, T>();

return { get, set, getWithPath, setWithPath };
return { get, set, getWithPath, setWithPath, clear };

function clear() {
directoryPathMap.clear();
}

function get(directory: string): T | undefined {
return getWithPath(toPath(directory, currentDirectory, getCanonicalFileName));
Expand Down Expand Up @@ -1242,7 +1283,6 @@ export function toPerDirectoryAndNonRelativeNameCache<T, U, V>(

interface ModuleOrTypeReferenceResolutionCache<T> extends PerDirectoryResolutionCache<T>, NonRelativeNameResolutionCache<T>, PackageJsonInfoCache {
getPackageJsonInfoCache(): PackageJsonInfoCache;
clearAllExceptPackageJsonInfoCache(): void;
}
function createModuleOrTypeReferenceResolutionCache<T>(
currentDirectory: string,
Expand All @@ -1261,18 +1301,13 @@ function createModuleOrTypeReferenceResolutionCache<T>(
clear,
update,
getPackageJsonInfoCache: () => packageJsonInfoCache!,
clearAllExceptPackageJsonInfoCache,
setOldResolutionCache,
};

function clear() {
clearAllExceptPackageJsonInfoCache();
packageJsonInfoCache!.clear();
}

function clearAllExceptPackageJsonInfoCache() {
perDirectory.clear();
perNonRelativeName.clear();
packageJsonInfoCache!.clear();
}

function update(options: CompilerOptions) {
Expand Down Expand Up @@ -2189,15 +2224,51 @@ export interface PackageJsonInfoContents {
/** false: resolved to nothing. undefined: not yet resolved */
resolvedEntrypoints: string[] | false | undefined;
}
/** @internal */
export function getPackageScope(
dir: string,
packageJsonInfoCache: PackageJsonInfoCache | undefined,
host: ModuleResolutionHost,
options: CompilerOptions,
): PackageJsonScope {
const state = getTemporaryModuleResolutionState(packageJsonInfoCache, host, options);
const failedLookupLocations: string[] = [];
const affectingLocations: string[] = [];
state.failedLookupLocations = failedLookupLocations;
state.affectingLocations = affectingLocations;
const result: PackageJsonScope = forEachAncestorDirectory(dir, directory => {
const fromCache = state.packageJsonInfoCache?.getPackageJsonScope(directory);
if (fromCache) {
const { host, traceEnabled } = state;
if (traceEnabled) {
trace(
host,
fromCache.info ?
Diagnostics.Directory_0_resolves_to_1_scope_according_to_cache :
Diagnostics.Directory_0_has_no_containing_package_json_scope_according_to_cache,
directory,
getPackageJsonLocationFromScope(fromCache)
);
}
return fromCache;
}

const info = getPackageJsonInfo(directory, /*onlyRecordFailures*/ false, state);
if (info) return { info };
}) || { info: undefined };
result.failedLookupLocations = updateResolutionField(result.failedLookupLocations, !options.cacheResolutions || !result.info ? failedLookupLocations : undefined);
result.affectingLocations = updateResolutionField(result.affectingLocations, affectingLocations);
packageJsonInfoCache?.setPackageJsonScope(dir, result);
return result;
}

/**
* A function for locating the package.json scope for a given path
*
* @internal
*/
export function getPackageScopeForPath(fileName: string, state: ModuleResolutionState): PackageJsonInfo | undefined {
const parts = getPathComponents(fileName);
parts.pop();
export function getPackageScopeForPath(dir: string, state: ModuleResolutionState): PackageJsonInfo | undefined {
const parts = getPathComponents(dir);
while (parts.length > 0) {
const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state);
if (pkg) {
Expand Down Expand Up @@ -2362,7 +2433,7 @@ function noKeyStartsWithDot(obj: MapLike<unknown>) {
}

function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult<Resolved> {
const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.());
const directoryPath = getNormalizedAbsolutePath(directory, state.host.getCurrentDirectory?.());
const scope = getPackageScopeForPath(directoryPath, state);
if (!scope || !scope.contents.packageJsonContent.exports) {
return undefined;
Expand Down Expand Up @@ -2423,7 +2494,7 @@ function loadModuleFromImports(extensions: Extensions, moduleName: string, direc
}
return toSearchResult(/*value*/ undefined);
}
const directoryPath = getNormalizedAbsolutePath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.());
const directoryPath = getNormalizedAbsolutePath(directory, state.host.getCurrentDirectory?.());
const scope = getPackageScopeForPath(directoryPath, state);
if (!scope) {
if (state.traceEnabled) {
Expand Down
Loading

0 comments on commit 7bb44e7

Please sign in to comment.