Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@
AffectedFileResult,
append,
arrayFrom,
arrayIsEqualTo,
arrayToMap,
BuilderProgram,
BuilderProgramHost,
BuilderState,
BuildInfo,
BuildInfoFileVersionMap,
CancellationToken,
combinePaths,
CommandLineOption,
compareStringsCaseSensitive,
compareValues,
Expand Down Expand Up @@ -60,6 +62,7 @@
isIncrementalCompilation,
isJsonSourceFile,
isNumber,
isPackageJsonInfo,
isString,
map,
mapDefinedIterator,
Expand All @@ -80,6 +83,8 @@
skipAlias,
skipTypeCheckingIgnoringNoCheck,
some,
sortAndDeduplicate,
SortedReadonlyArray,
SourceFile,
sourceFileMayBeEmitted,
SourceMapEmitResult,
Expand Down Expand Up @@ -180,6 +185,7 @@
latestChangedDtsFile: string | undefined;
/** Recorded if program had errors */
hasErrors?: boolean;
packageJsons?: SortedReadonlyArray<string>;
}

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

interface BuilderProgramStateWithDefinedProgram extends BuilderProgramState {
Expand Down Expand Up @@ -365,6 +372,7 @@
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 @@
latestChangedDtsFile?: string | undefined;
errors: true | undefined;
checkPending: true | undefined;
packageJsons: string[] | undefined;
}

/** @internal */
Expand Down Expand Up @@ -1187,6 +1196,7 @@
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 ensurePackageJsonsForState(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost) {
if (state.packageJsons !== undefined) return;
let packageJsons: string[] = [];

Check failure on line 1232 in src/compiler/builder.ts

View workflow job for this annotation

GitHub Actions / lint

'packageJsons' is never reassigned. Use 'const' instead
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 @@
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 @@
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 @@
latestChangedDtsFile,
errors: state.hasErrors ? true : undefined,
checkPending: state.checkPending,
packageJsons: toPackageJsons(),
version,
};
return buildInfo;
Expand Down Expand Up @@ -1564,6 +1600,10 @@
}
return changeFileSet;
}

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

/** @internal */
Expand Down Expand Up @@ -1683,7 +1723,7 @@
}

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 @@
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 @@

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 @@
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 @@
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
hasErrors: buildInfo.errors,
checkPending: buildInfo.checkPending,
packageJsons: toPackageJsons(),
};
}

Expand Down Expand Up @@ -2389,6 +2431,10 @@
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
47 changes: 24 additions & 23 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
AffectedFileResult,
arrayFrom,

Check warning on line 3 in src/compiler/tsbuildPublic.ts

View workflow job for this annotation

GitHub Actions / lint

'arrayFrom' is defined but never used. Allowed unused vars must match /^(_+$|_[^_])/u
assertType,
BuilderProgram,
BuildInfo,
Expand All @@ -11,7 +11,7 @@
clearMap,
closeFileWatcher,
closeFileWatcherOf,
combinePaths,

Check warning on line 14 in src/compiler/tsbuildPublic.ts

View workflow job for this annotation

GitHub Actions / lint

'combinePaths' is defined but never used. Allowed unused vars must match /^(_+$|_[^_])/u
commonOptionsWithBuild,
CompilerHost,
CompilerOptions,
Expand Down Expand Up @@ -49,7 +49,7 @@
flattenDiagnosticMessageText,
forEach,
forEachEntry,
forEachKey,

Check warning on line 52 in src/compiler/tsbuildPublic.ts

View workflow job for this annotation

GitHub Actions / lint

'forEachKey' is defined but never used. Allowed unused vars must match /^(_+$|_[^_])/u
ForegroundColorEscapeSequences,
formatColorAndReset,
getAllProjectOutputs,
Expand Down Expand Up @@ -79,7 +79,7 @@
isIgnoredFileFromWildCardWatching,
isIncrementalBuildInfo,
isIncrementalCompilation,
isPackageJsonInfo,

Check warning on line 82 in src/compiler/tsbuildPublic.ts

View workflow job for this annotation

GitHub Actions / lint

'isPackageJsonInfo' is defined but never used. Allowed unused vars must match /^(_+$|_[^_])/u
isSolutionConfig,
loadWithModeAwareCache,
maybeBind,
Expand Down Expand Up @@ -415,11 +415,11 @@
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 +539,7 @@
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 +678,7 @@
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 +1054,6 @@
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 +1758,17 @@
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 packageJsonStatus = forEach(
(buildInfo as IncrementalBuildInfo | NonIncrementalBuildInfo).packageJsons,
packageJson =>
checkConfigFileUpToDateStatus(
state,
getNormalizedAbsolutePath(packageJson, getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()))),
oldestOutputFileTime,
oldestOutputFileName,
),
);
if (dependentPackageFileStatus) return dependentPackageFileStatus;
if (packageJsonStatus) return packageJsonStatus;

// Up to date
return {
Expand Down Expand Up @@ -2199,10 +2192,18 @@
}

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);
mutateMap(
getOrCreateValueMapFromConfigFileMap(state.allWatchedPackageJsonFiles, resolvedPath),
state.lastCachedPackageJsonLookups.get(resolvedPath),
new Set(
buildInfoCacheEntry?.buildInfo ?
(buildInfoCacheEntry.buildInfo as IncrementalBuildInfo | NonIncrementalBuildInfo).packageJsons?.map(
f => getNormalizedAbsolutePath(f, getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, state.host.getCurrentDirectory()))),
) :
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
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,20 @@ export const api = ky.extend({});


//// [/home/src/workspaces/project/tsconfig.tsbuildinfo]
{"root":["./index.ts"],"errors":true,"version":"FakeTSVersion"}
{"root":["./index.ts"],"errors":true,"packageJsons":["./node_modules/ky/package.json","./package.json"],"version":"FakeTSVersion"}

//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt]
{
"root": [
"./index.ts"
],
"errors": true,
"packageJsons": [
"./node_modules/ky/package.json",
"./package.json"
],
"version": "FakeTSVersion",
"size": 63
"size": 130
}


Expand Down
Loading
Loading