Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 52 additions & 6 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {
AffectedFileResult,
append,
arrayFrom,
arrayIsEqualTo,
arrayToMap,
BuilderProgram,
BuilderProgramHost,
BuilderState,
BuildInfo,
BuildInfoFileVersionMap,
CancellationToken,
combinePaths,
CommandLineOption,
compareStringsCaseSensitive,
compareValues,
Expand Down Expand Up @@ -60,6 +62,7 @@ import {
isIncrementalCompilation,
isJsonSourceFile,
isNumber,
isPackageJsonInfo,
isString,
map,
mapDefinedIterator,
Expand All @@ -80,6 +83,8 @@ import {
skipAlias,
skipTypeCheckingIgnoringNoCheck,
some,
sortAndDeduplicate,
SortedReadonlyArray,
SourceFile,
sourceFileMayBeEmitted,
SourceMapEmitResult,
Expand Down Expand Up @@ -180,6 +185,7 @@ export interface ReusableBuilderProgramState extends BuilderState {
latestChangedDtsFile: string | undefined;
/** Recorded if program had errors */
hasErrors?: boolean;
packageJsons?: SortedReadonlyArray<string>;
}

// dprint-ignore
Expand Down Expand Up @@ -260,6 +266,7 @@ export interface BuilderProgramState extends BuilderState, ReusableBuilderProgra
/** Already seen program emit */
seenProgramEmit: BuilderFileEmit | undefined;
hasErrorsFromOldState?: boolean;
packageJsonsFromOldState?: SortedReadonlyArray<string>;
}

interface BuilderProgramStateWithDefinedProgram extends BuilderProgramState {
Expand Down Expand Up @@ -365,6 +372,7 @@ function createBuilderProgramState(
canCopyEmitDiagnostics = false;
}
state.hasErrorsFromOldState = oldState!.hasErrors;
state.packageJsonsFromOldState = oldState!.packageJsons;
}
else {
// We arent using old state, so atleast emit buildInfo with current information
Expand Down Expand Up @@ -1139,6 +1147,7 @@ export interface IncrementalBuildInfoBase extends BuildInfo {
latestChangedDtsFile?: string | undefined;
errors: true | undefined;
checkPending: true | undefined;
packageJsons: string[] | undefined;
}

/** @internal */
Expand Down Expand Up @@ -1187,6 +1196,7 @@ export interface NonIncrementalBuildInfo extends BuildInfo {
root: readonly string[];
errors: true | undefined;
checkPending: true | undefined;
packageJsons: string[] | undefined;
}

function isNonIncrementalBuildInfo(info: BuildInfo): info is NonIncrementalBuildInfo {
Expand Down Expand Up @@ -1217,22 +1227,44 @@ function ensureHasErrorsForState(state: BuilderProgramStateWithDefinedProgram) {
}
}

function ensurePackageJsonsForState(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost) {
if (state.packageJsons !== undefined) return;
const packageJsons: string[] = [];
if (state.program.getCompilerOptions().configFilePath) {
const internalMap = state.program.getModuleResolutionCache()?.getPackageJsonInfoCache().getInternalMap();
if (internalMap) {
internalMap.forEach(value => {
if (isPackageJsonInfo(value)) {
let path = combinePaths(value.packageDirectory, "package.json");
if (host.realpath) {
path = host.realpath(path);
}
packageJsons.push(path);
}
});
}
}
state.packageJsons = sortAndDeduplicate(packageJsons);
}

function hasSyntaxOrGlobalErrors(state: BuilderProgramStateWithDefinedProgram) {
return !!state.program.getConfigFileParsingDiagnostics().length ||
!!state.program.getSyntacticDiagnostics().length ||
!!state.program.getOptionsDiagnostics().length ||
!!state.program.getGlobalDiagnostics().length;
}

function getBuildInfoEmitPending(state: BuilderProgramStateWithDefinedProgram) {
function getBuildInfoEmitPending(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost) {
ensureHasErrorsForState(state);
return state.buildInfoEmitPending ??= !!state.hasErrorsFromOldState !== !!state.hasErrors;
ensurePackageJsonsForState(state, host);
return state.buildInfoEmitPending ??= !!state.hasErrorsFromOldState !== !!state.hasErrors ||
!arrayIsEqualTo(state.packageJsons, state.packageJsonsFromOldState);
}

/**
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
*/
function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
function getBuildInfo(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost): BuildInfo {
const currentDirectory = state.program.getCurrentDirectory();
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
// Convert the file name to Path here if we set the fileName instead to optimize multiple d.ts file emits and having to compute Canonical path
Expand All @@ -1241,11 +1273,13 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
const fileNameToFileId = new Map<string, IncrementalBuildInfoFileId>();
const rootFileNames = new Set(state.program.getRootFileNames().map(f => toPath(f, currentDirectory, state.program.getCanonicalFileName)));
ensureHasErrorsForState(state);
ensurePackageJsonsForState(state, host);
if (!isIncrementalCompilation(state.compilerOptions)) {
const buildInfo: NonIncrementalBuildInfo = {
root: arrayFrom(rootFileNames, r => relativeToBuildInfo(r)),
errors: state.hasErrors ? true : undefined,
checkPending: state.checkPending,
packageJsons: toPackageJsons(),
version,
};
return buildInfo;
Expand Down Expand Up @@ -1281,6 +1315,7 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
state.programEmitPending, // Actual value
errors: state.hasErrors ? true : undefined,
checkPending: state.checkPending,
packageJsons: toPackageJsons(),
version,
};
return buildInfo;
Expand Down Expand Up @@ -1373,6 +1408,7 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
latestChangedDtsFile,
errors: state.hasErrors ? true : undefined,
checkPending: state.checkPending,
packageJsons: toPackageJsons(),
version,
};
return buildInfo;
Expand Down Expand Up @@ -1564,6 +1600,10 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
}
return changeFileSet;
}

function toPackageJsons() {
return state.packageJsons?.length ? state.packageJsons.map(relativeToBuildInfo) : undefined;
}
}

/** @internal */
Expand Down Expand Up @@ -1683,7 +1723,7 @@ export function createBuilderProgram(
}

const state = createBuilderProgramState(newProgram, oldState);
newProgram.getBuildInfo = () => getBuildInfo(toBuilderProgramStateWithDefinedProgram(state));
newProgram.getBuildInfo = () => getBuildInfo(toBuilderProgramStateWithDefinedProgram(state), host);

// To ensure that we arent storing any references to old program or new program without state
newProgram = undefined!;
Expand Down Expand Up @@ -1722,7 +1762,7 @@ export function createBuilderProgram(
cancellationToken: CancellationToken | undefined,
): EmitResult {
Debug.assert(isBuilderProgramStateWithDefinedProgram(state));
if (getBuildInfoEmitPending(state)) {
if (getBuildInfoEmitPending(state, host)) {
const result = state.program.emitBuildInfo(
writeFile || maybeBind(host, host.writeFile),
cancellationToken,
Expand Down Expand Up @@ -1809,7 +1849,7 @@ export function createBuilderProgram(

if (!affected) {
// Emit buildinfo if pending
if (isForDtsErrors || !getBuildInfoEmitPending(state)) return undefined;
if (isForDtsErrors || !getBuildInfoEmitPending(state, host)) return undefined;
const affected = state.program;
const result = affected.emitBuildInfo(
writeFile || maybeBind(host, host.writeFile),
Expand Down Expand Up @@ -2282,6 +2322,7 @@ export function createBuilderProgramUsingIncrementalBuildInfo(
programEmitPending: buildInfo.pendingEmit === undefined ? undefined : toProgramEmitPending(buildInfo.pendingEmit, buildInfo.options),
hasErrors: buildInfo.errors,
checkPending: buildInfo.checkPending,
packageJsons: toPackageJsons(),
};
}
else {
Expand Down Expand Up @@ -2320,6 +2361,7 @@ export function createBuilderProgramUsingIncrementalBuildInfo(
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
hasErrors: buildInfo.errors,
checkPending: buildInfo.checkPending,
packageJsons: toPackageJsons(),
};
}

Expand Down Expand Up @@ -2389,6 +2431,10 @@ export function createBuilderProgramUsingIncrementalBuildInfo(
function toPerFileEmitDiagnostics(diagnostics: readonly IncrementalBuildInfoEmitDiagnostic[] | undefined): Map<Path, readonly ReusableDiagnostic[]> | undefined {
return diagnostics && arrayToMap(diagnostics, value => toFilePath(value[0]), value => value[1]);
}

function toPackageJsons() {
return buildInfo.packageJsons?.map(toAbsolutePath) as unknown as SortedReadonlyArray<string> ?? emptyArray;
}
}

/** @internal */
Expand Down
1 change: 1 addition & 0 deletions src/compiler/builderPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface BuilderProgramHost {
* this callback if present would be used to write files
*/
writeFile?: WriteFileCallback;
realpath?(path: string): string;
/**
* Store information about the signature
*
Expand Down
54 changes: 27 additions & 27 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
AffectedFileResult,
arrayFrom,
assertType,
BuilderProgram,
BuildInfo,
Expand All @@ -11,7 +10,6 @@ import {
clearMap,
closeFileWatcher,
closeFileWatcherOf,
combinePaths,
commonOptionsWithBuild,
CompilerHost,
CompilerOptions,
Expand Down Expand Up @@ -49,7 +47,6 @@ import {
flattenDiagnosticMessageText,
forEach,
forEachEntry,
forEachKey,
ForegroundColorEscapeSequences,
formatColorAndReset,
getAllProjectOutputs,
Expand Down Expand Up @@ -79,10 +76,10 @@ import {
isIgnoredFileFromWildCardWatching,
isIncrementalBuildInfo,
isIncrementalCompilation,
isPackageJsonInfo,
isSolutionConfig,
loadWithModeAwareCache,
maybeBind,
memoize,
missingFileModifiedTime,
ModuleResolutionCache,
mutateMap,
Expand Down Expand Up @@ -415,11 +412,11 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
readonly allWatchedInputFiles: Map<ResolvedConfigFilePath, Map<string, FileWatcher>>;
readonly allWatchedConfigFiles: Map<ResolvedConfigFilePath, FileWatcher>;
readonly allWatchedExtendedConfigFiles: Map<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>;
readonly allWatchedPackageJsonFiles: Map<ResolvedConfigFilePath, Map<Path, FileWatcher>>;
readonly allWatchedPackageJsonFiles: Map<ResolvedConfigFilePath, Map<string, FileWatcher>>;
readonly filesWatched: Map<Path, FileWatcherWithModifiedTime | Date>;
readonly outputTimeStamps: Map<ResolvedConfigFilePath, Map<Path, Date>>;

readonly lastCachedPackageJsonLookups: Map<ResolvedConfigFilePath, Set<string> | undefined>;
readonly allWatchedPackageJsons: Map<ResolvedConfigFilePath, Set<string> | undefined>;

timerToBuildInvalidatedProject: any;
reportFileChangeDetected: boolean;
Expand Down Expand Up @@ -539,8 +536,7 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
allWatchedExtendedConfigFiles: new Map(),
allWatchedPackageJsonFiles: new Map(),
filesWatched: new Map(),

lastCachedPackageJsonLookups: new Map(),
allWatchedPackageJsons: new Map(),

timerToBuildInvalidatedProject: undefined,
reportFileChangeDetected: false,
Expand Down Expand Up @@ -679,7 +675,7 @@ function createStateBuildOrder<T extends BuilderProgram>(state: SolutionBuilderS
mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.buildInfoCache, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.outputTimeStamps, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.lastCachedPackageJsonLookups, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.allWatchedPackageJsons, currentProjects, noopOnDelete);

// Remove watches for the program no longer in the solution
if (state.watch) {
Expand Down Expand Up @@ -1055,17 +1051,6 @@ function createBuildOrUpdateInvalidedProject<T extends BuilderProgram>(
config.projectReferences,
);
if (state.watch) {
const internalMap = state.moduleResolutionCache?.getPackageJsonInfoCache().getInternalMap();
state.lastCachedPackageJsonLookups.set(
projectPath,
internalMap && new Set(arrayFrom(
internalMap.values(),
data =>
state.host.realpath && (isPackageJsonInfo(data) || data.directoryExists) ?
state.host.realpath(combinePaths(data.packageDirectory, "package.json")) :
combinePaths(data.packageDirectory, "package.json"),
)),
);
state.builderPrograms.set(projectPath, program);
}
step++;
Expand Down Expand Up @@ -1770,12 +1755,18 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
if (extendedConfigStatus) return extendedConfigStatus;

// Check package file time
const packageJsonLookups = state.lastCachedPackageJsonLookups.get(resolvedPath);
const dependentPackageFileStatus = packageJsonLookups && forEachKey(
packageJsonLookups,
path => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName),
const getBuildInfoDirectory = memoize(() => getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())));
const packageJsonStatus = forEach(
(buildInfo as IncrementalBuildInfo | NonIncrementalBuildInfo).packageJsons,
packageJson =>
checkConfigFileUpToDateStatus(
state,
getNormalizedAbsolutePath(packageJson, getBuildInfoDirectory()),
oldestOutputFileTime,
oldestOutputFileName,
),
);
if (dependentPackageFileStatus) return dependentPackageFileStatus;
if (packageJsonStatus) return packageJsonStatus;

// Up to date
return {
Expand Down Expand Up @@ -2199,10 +2190,19 @@ function watchInputFiles<T extends BuilderProgram>(state: SolutionBuilderState<T
}

function watchPackageJsonFiles<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) {
if (!state.watch || !state.lastCachedPackageJsonLookups) return;
if (!state.watch) return;
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(parsed.options)!;
const buildInfoCacheEntry = getBuildInfoCacheEntry(state, buildInfoPath, resolvedPath);
const getBuildInfoDirectory = memoize(() => getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, state.host.getCurrentDirectory())));
mutateMap(
getOrCreateValueMapFromConfigFileMap(state.allWatchedPackageJsonFiles, resolvedPath),
state.lastCachedPackageJsonLookups.get(resolvedPath),
new Set(
buildInfoCacheEntry?.buildInfo ?
(buildInfoCacheEntry.buildInfo as IncrementalBuildInfo | NonIncrementalBuildInfo).packageJsons?.map(
f => getNormalizedAbsolutePath(f, getBuildInfoDirectory()),
) :
undefined,
),
{
createNewValue: input =>
watchFile(
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9672,6 +9672,7 @@ declare namespace ts {
* this callback if present would be used to write files
*/
writeFile?: WriteFileCallback;
realpath?(path: string): string;
}
/**
* Builder to manage the program state changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const api = ky.extend({});


//// [/home/src/workspaces/project/tsconfig.tsbuildinfo]
{"fileNames":["../../tslibs/ts/lib/lib.esnext.full.d.ts","./node_modules/ky/distribution/index.d.ts","./index.ts"],"fileIdsList":[[2]],"fileInfos":[{"version":"3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedFormat":1},{"version":"10101889135-type KyInstance = {\n extend(options: Record<string,unknown>): KyInstance;\n}\ndeclare const ky: KyInstance;\nexport default ky;\n","impliedFormat":99},{"version":"-383421929-import ky from 'ky';\nexport const api = ky.extend({});\n","impliedFormat":99}],"root":[3],"options":{"declaration":true,"module":199,"skipDefaultLibCheck":true,"skipLibCheck":true},"referencedMap":[[3,1]],"emitDiagnosticsPerFile":[[3,[{"start":34,"length":3,"messageText":"Exported variable 'api' has or is using name 'KyInstance' from external module \"/home/src/workspaces/project/node_modules/ky/distribution/index\" but cannot be named.","category":1,"code":4023}]]],"version":"FakeTSVersion"}
{"fileNames":["../../tslibs/ts/lib/lib.esnext.full.d.ts","./node_modules/ky/distribution/index.d.ts","./index.ts"],"fileIdsList":[[2]],"fileInfos":[{"version":"3858781397-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedFormat":1},{"version":"10101889135-type KyInstance = {\n extend(options: Record<string,unknown>): KyInstance;\n}\ndeclare const ky: KyInstance;\nexport default ky;\n","impliedFormat":99},{"version":"-383421929-import ky from 'ky';\nexport const api = ky.extend({});\n","impliedFormat":99}],"root":[3],"options":{"declaration":true,"module":199,"skipDefaultLibCheck":true,"skipLibCheck":true},"referencedMap":[[3,1]],"emitDiagnosticsPerFile":[[3,[{"start":34,"length":3,"messageText":"Exported variable 'api' has or is using name 'KyInstance' from external module \"/home/src/workspaces/project/node_modules/ky/distribution/index\" but cannot be named.","category":1,"code":4023}]]],"packageJsons":["./node_modules/ky/package.json","./package.json"],"version":"FakeTSVersion"}

//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt]
{
Expand Down Expand Up @@ -166,8 +166,12 @@ export const api = ky.extend({});
]
]
],
"packageJsons": [
"./node_modules/ky/package.json",
"./package.json"
],
"version": "FakeTSVersion",
"size": 1345
"size": 1412
}


Expand Down
Loading
Loading