Skip to content

Commit

Permalink
Store package.json for the directories in buildinfo
Browse files Browse the repository at this point in the history
  • Loading branch information
sheetalkamat committed Dec 9, 2022
1 parent 2ae25d4 commit 592a8bd
Show file tree
Hide file tree
Showing 21 changed files with 2,048 additions and 282 deletions.
39 changes: 39 additions & 0 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
createBuildInfo,
createGetCanonicalFileName,
createPerDirectoryAndNonRelativeNameCache,
createPerNonRelativeNameCache,
createProgram,
CustomTransformers,
Debug,
Expand Down Expand Up @@ -58,12 +59,14 @@ import {
getTsBuildInfoEmitOutputFilePath,
handleNoEmitOptions,
HostForComputeHash,
identity,
isArray,
isDeclarationFileName,
isExternalModuleNameRelative,
isJsonSourceFile,
isNumber,
isString,
last,
map,
mapDefined,
maybeBind,
Expand All @@ -79,6 +82,7 @@ import {
PackageJsonInfoCache,
Path,
PerDirectoryAndNonRelativeNameCache,
PerNonRelativeNameCache,
Program,
ProjectReference,
ReadBuildProgramHost,
Expand Down Expand Up @@ -182,6 +186,7 @@ export interface ReusableBuilderProgramState extends BuilderState {
cacheResolutions?: {
modules: PerDirectoryAndNonRelativeNameCache<ResolvedModuleWithFailedLookupLocations> | undefined;
typeRefs: PerDirectoryAndNonRelativeNameCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations> | undefined;
packageJsons: Map<Path, string> | undefined;
packageJsonCache: PackageJsonInfoCache | undefined;
};
resuableCacheResolutions?: {
Expand Down Expand Up @@ -986,13 +991,16 @@ export type ProgramBuildInfoResolutionCacheWithRedirects = ProgramBuildInfoResol
redirects: readonly ProgramBuildInfoResolutionRedirectsCache[];
};
/** @internal */
export type ProgramBuildInfoPackageJson = ProgramBuildInfoAbsoluteFileId | [dirId: ProgramBuildInfoFileId, packageJson: ProgramBuildInfoAbsoluteFileId];
/** @internal */
export interface ProgramBuildInfoCacheResolutions {
resolutions: readonly ProgramBuildInfoResolution[];
names: readonly string[];
hash: readonly ProgramBuildInfoHash[] | undefined;
resolutionEntries: readonly ProgramBuildInfoResolutionEntry[];
modules: ProgramBuildInfoResolutionCacheWithRedirects | undefined;
typeRefs: ProgramBuildInfoResolutionCacheWithRedirects | undefined;
packageJsons: readonly ProgramBuildInfoPackageJson[] | undefined;
}
/** @internal */
export interface ProgramMultiFileEmitBuildInfo {
Expand Down Expand Up @@ -1313,6 +1321,7 @@ function getBuildInfo(state: BuilderProgramState, host: BuilderProgramHost, bund
if (!resolutions) return;
Debug.assertIsDefined(names);
Debug.assertIsDefined(resolutionEntries);
const packageJsons = toProgramBuildInfoPackageJsons(cacheResolutions?.packageJsons);
state.resuableCacheResolutions = {
cache: {
resolutions,
Expand All @@ -1321,6 +1330,7 @@ function getBuildInfo(state: BuilderProgramState, host: BuilderProgramHost, bund
resolutionEntries,
modules,
typeRefs,
packageJsons,
},
getProgramBuildInfoFilePathDecoder: memoize(() => getProgramBuildInfoFilePathDecoder(
fileNames,
Expand All @@ -1332,6 +1342,20 @@ function getBuildInfo(state: BuilderProgramState, host: BuilderProgramHost, bund
return state.resuableCacheResolutions.cache;
}

function toProgramBuildInfoPackageJsons(cache: Map<Path, string> | undefined): readonly ProgramBuildInfoPackageJson[] | undefined {
let result: ProgramBuildInfoPackageJson[] | undefined;
cache?.forEach((packageJson, dirPath) => {
const packageJsonDirPath = getDirectoryPath(toPath(packageJson, currentDirectory, state.program!.getCanonicalFileName));
(result ??= []).push(packageJsonDirPath === dirPath ?
toAbsoluteFileId(packageJson) :
[
toFileId(dirPath),
toAbsoluteFileId(packageJson),
]);
});
return result;
}

function toProgramBuildInfoResolutionCacheWithRedirects<T extends ResolvedModuleWithFailedLookupLocations | ResolvedTypeReferenceDirectiveWithFailedLookupLocations>(
cache: PerDirectoryAndNonRelativeNameCache<T> | undefined
): ProgramBuildInfoResolutionCacheWithRedirects | undefined {
Expand Down Expand Up @@ -1450,9 +1474,23 @@ function getCacheResolutions(state: BuilderProgramState) {
if (state.cacheResolutions || !state.compilerOptions.cacheResolutions) return state.cacheResolutions;
let modules: PerDirectoryAndNonRelativeNameCache<ResolvedModuleWithFailedLookupLocations> | undefined;
let typeRefs: PerDirectoryAndNonRelativeNameCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations> | undefined;
let packageJsons: Map<Path, string> | undefined;
let nonRelativePackageJsons: PerNonRelativeNameCache<string> | 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) {
const dirPath = getDirectoryPath(f.resolvedPath);
if (!nonRelativePackageJsons?.getWithPath(dirPath)) {
const result = last(f.packageJsonLocations!);
(packageJsons ??= new Map()).set(dirPath, result);
(nonRelativePackageJsons ??= createPerNonRelativeNameCache(
state.program!.getCurrentDirectory(),
state.program!.getCanonicalFileName,
identity,
)).setWithPath(dirPath, result, ancestorPath => packageJsons!.delete(ancestorPath));
}
}
}
const automaticTypeDirectiveNames = state.program!.getAutomaticTypeDirectiveNames();
if (automaticTypeDirectiveNames.length) {
Expand All @@ -1462,6 +1500,7 @@ function getCacheResolutions(state: BuilderProgramState) {
return state.cacheResolutions = {
modules,
typeRefs,
packageJsons,
packageJsonCache: state.program!.getModuleResolutionCache()?.getPackageJsonInfoCache().clone(),
};
}
Expand Down
161 changes: 85 additions & 76 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,90 @@ export function getOriginalOrResolvedTypeReferenceFileName(result: ResolvedTypeR
(result.resolvedTypeReferenceDirective.originalPath || result.resolvedTypeReferenceDirective.resolvedFileName);
}

/** @internal */
export function createPerNonRelativeNameCache<T>(
currentDirectory: string,
getCanonicalFileName: (s: string) => string,
getResolvedFileName: (result: T) => string | undefined,
): PerNonRelativeNameCache<T> {
const directoryPathMap = new Map<Path, T>();

return { get, set, getWithPath, setWithPath };

function get(directory: string): T | undefined {
return getWithPath(toPath(directory, currentDirectory, getCanonicalFileName));
}

function set(directory: string, result: T): void {
return setWithPath(toPath(directory, currentDirectory, getCanonicalFileName), result, noop);
}

function getWithPath(directory: Path): T | undefined {
return directoryPathMap.get(directory);
}

/**
* At first this function add entry directory -> module resolution result to the table.
* Then it computes the set of parent folders for 'directory' that should have the same module resolution result
* and for every parent folder in set it adds entry: parent -> module resolution. .
* Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts.
* Set of parent folders that should have the same result will be:
* [
* /a/b/c/d, /a/b/c, /a/b
* ]
* this means that request for module resolution from file in any of these folder will be immediately found in cache.
*/
function setWithPath(path: Path, result: T, ancestoryWorker: (directory: Path) => void): void {
// if entry is already in cache do nothing
if (directoryPathMap.has(path)) return;
directoryPathMap.set(path, result);

const resolvedFileName = getResolvedFileName(result);
// find common prefix between directory and resolved file name
// this common prefix should be the shortest path that has the same resolution
// directory: /a/b/c/d/e
// resolvedFileName: /a/b/foo.d.ts
// commonPrefix: /a/b
// for failed lookups cache the result for every directory up to root
const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName);
let current = path;
while (current !== commonPrefix) {
const parent = getDirectoryPath(current);
if (parent === current) break;
if (directoryPathMap.has(parent)) {
ancestoryWorker(parent);
break;
}
directoryPathMap.set(parent, result);
ancestoryWorker(parent);
current = parent;
}
}

function getCommonPrefix(directory: Path, resolution: string) {
const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName);

// find first position where directory and resolution differs
let i = 0;
const limit = Math.min(directory.length, resolutionDirectory.length);
while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) {
i++;
}
if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) {
return directory;
}
const rootLength = getRootLength(directory);
if (i < rootLength) {
return undefined;
}
const sep = directory.lastIndexOf(directorySeparator, i - 1);
if (sep === -1) {
return undefined;
}
return directory.substr(0, Math.max(sep, rootLength));
}
}

function createNonRelativeNameResolutionCache<T>(
currentDirectory: string,
getCanonicalFileName: (s: string) => string,
Expand Down Expand Up @@ -1079,82 +1163,7 @@ function createNonRelativeNameResolutionCache<T>(
}

function createPerModuleNameCache(): PerNonRelativeNameCache<T> {
const directoryPathMap = new Map<Path, T>();

return { get, set, getWithPath, setWithPath };

function get(directory: string): T | undefined {
return getWithPath(toPath(directory, currentDirectory, getCanonicalFileName));
}

function set(directory: string, result: T): void {
return setWithPath(toPath(directory, currentDirectory, getCanonicalFileName), result, noop);
}

function getWithPath(directory: Path): T | undefined {
return directoryPathMap.get(directory);
}

/**
* At first this function add entry directory -> module resolution result to the table.
* Then it computes the set of parent folders for 'directory' that should have the same module resolution result
* and for every parent folder in set it adds entry: parent -> module resolution. .
* Lets say we first directory name: /a/b/c/d/e and resolution result is: /a/b/bar.ts.
* Set of parent folders that should have the same result will be:
* [
* /a/b/c/d, /a/b/c, /a/b
* ]
* this means that request for module resolution from file in any of these folder will be immediately found in cache.
*/
function setWithPath(path: Path, result: T, ancestoryWorker: (directory: Path) => void): void {
// if entry is already in cache do nothing
if (directoryPathMap.has(path)) return;
directoryPathMap.set(path, result);

const resolvedFileName = getResolvedFileName(result);
// find common prefix between directory and resolved file name
// this common prefix should be the shortest path that has the same resolution
// directory: /a/b/c/d/e
// resolvedFileName: /a/b/foo.d.ts
// commonPrefix: /a/b
// for failed lookups cache the result for every directory up to root
const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName);
let current = path;
while (current !== commonPrefix) {
const parent = getDirectoryPath(current);
if (parent === current) break;
if (directoryPathMap.has(parent)) {
ancestoryWorker(parent);
break;
}
directoryPathMap.set(parent, result);
ancestoryWorker(parent);
current = parent;
}
}

function getCommonPrefix(directory: Path, resolution: string) {
const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName);

// find first position where directory and resolution differs
let i = 0;
const limit = Math.min(directory.length, resolutionDirectory.length);
while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) {
i++;
}
if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) {
return directory;
}
const rootLength = getRootLength(directory);
if (i < rootLength) {
return undefined;
}
const sep = directory.lastIndexOf(directorySeparator, i - 1);
if (sep === -1) {
return undefined;
}
return directory.substr(0, Math.max(sep, rootLength));
}
return createPerNonRelativeNameCache(currentDirectory, getCanonicalFileName, getResolvedFileName);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/testRunner/unittests/tsc/cacheResolutions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ describe("unittests:: tsc:: cacheResolutions::", () => {
{
caption: "Delete package.json",
edit: fs => fs.unlinkSync(`/src/projects/project/package.json`),
discrepancyExplanation: () => [
`Buildinfo is not re-written so it has package.json map from before in incremental and no package.json map in clean build`
]
},
{
caption: "Add package json file with type module",
Expand Down
11 changes: 10 additions & 1 deletion src/testRunner/unittests/tsc/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,14 +530,16 @@ type ReadableProgramBuildInfoResolutionCacheWithRedirects = ReadableProgramBuild
redirects: readonly ReadableProgramBuildInfoResolutionRedirectsCache[];
};
type ReadableProgramBuildInfoHash = string | [file: string, hash: string];
type ReadableProgramBuildInfoPackageJson = string | [dir: string, packageJson: string];
type ReadableProgramBuildInfoCacheResolutions = Omit<ts.ProgramBuildInfoCacheResolutions,
"resolutions" | "hash" | "resolutionEntries" | "modules" | "typeRefs"
"resolutions" | "hash" | "resolutionEntries" | "modules" | "typeRefs" | "packageJsons"
> & {
resolutions: readonly ReadableWithOriginal<ReadableProgramBuildInfoResolution, ts.ProgramBuildInfoResolution>[];
hash: readonly ReadableProgramBuildInfoHash[] | undefined;
resolutionEntries: readonly ReadableWithOriginal<ReadableProgramBuildInfoResolutionEntry, ts.ProgramBuildInfoResolutionEntry>[];
modules: ReadableProgramBuildInfoResolutionCacheWithRedirects | undefined;
typeRefs: ReadableProgramBuildInfoResolutionCacheWithRedirects | undefined;
packageJsons: readonly ReadableProgramBuildInfoPackageJson[] | undefined;
};

type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo,
Expand Down Expand Up @@ -715,10 +717,17 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
resolutionEntries: resolutionEntries.withOriginals,
modules: toReadableProgramBuildInfoResolutionCacheWithRedirects(cacheResolutions.modules),
typeRefs: toReadableProgramBuildInfoResolutionCacheWithRedirects(cacheResolutions.typeRefs),
packageJsons: cacheResolutions.packageJsons?.map(toReadableProgramBuildInfoPackageJson),
hash: cacheResolutions.hash?.map(toReadableProgramBuildInfoHash),
};
}

function toReadableProgramBuildInfoPackageJson(entry: ts.ProgramBuildInfoPackageJson): ReadableProgramBuildInfoPackageJson {
return ts.isArray(entry) ?
[toFileName(entry[0]), toFileName(entry[1])] :
toFileName(entry);
}

function toReadableProgramBuildInfoHash(hash: ts.ProgramBuildInfoHash): ReadableProgramBuildInfoHash {
return ts.isArray(hash) ? [toFileName(hash[0]), hash[1]] : toFileName(hash);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ CleanBuild:
}
]
}
],
"packageJsons": [
"./node_modules/pkg0/package.json",
"./node_modules/pkg2/package.json",
"./node_modules/pkg3/package.json"
]
}
},
Expand Down Expand Up @@ -800,6 +805,11 @@ IncrementalBuild:
}
]
}
],
"packageJsons": [
"./node_modules/pkg0/package.json",
"./node_modules/pkg2/package.json",
"./node_modules/pkg3/package.json"
]
}
},
Expand Down
Loading

0 comments on commit 592a8bd

Please sign in to comment.