Skip to content

Commit 1a2d75d

Browse files
committed
Handle package.json for --build consistently
Fixes #48314
1 parent 7956c00 commit 1a2d75d

File tree

80 files changed

+876
-413
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+876
-413
lines changed

src/compiler/builder.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {
33
AffectedFileResult,
44
append,
55
arrayFrom,
6+
arrayIsEqualTo,
67
arrayToMap,
78
BuilderProgram,
89
BuilderProgramHost,
910
BuilderState,
1011
BuildInfo,
1112
BuildInfoFileVersionMap,
1213
CancellationToken,
14+
combinePaths,
1315
CommandLineOption,
1416
compareStringsCaseSensitive,
1517
compareValues,
@@ -60,6 +62,7 @@ import {
6062
isIncrementalCompilation,
6163
isJsonSourceFile,
6264
isNumber,
65+
isPackageJsonInfo,
6366
isString,
6467
map,
6568
mapDefinedIterator,
@@ -80,6 +83,8 @@ import {
8083
skipAlias,
8184
skipTypeCheckingIgnoringNoCheck,
8285
some,
86+
sortAndDeduplicate,
87+
SortedReadonlyArray,
8388
SourceFile,
8489
sourceFileMayBeEmitted,
8590
SourceMapEmitResult,
@@ -180,6 +185,7 @@ export interface ReusableBuilderProgramState extends BuilderState {
180185
latestChangedDtsFile: string | undefined;
181186
/** Recorded if program had errors */
182187
hasErrors?: boolean;
188+
packageJsons?: SortedReadonlyArray<string>;
183189
}
184190

185191
// dprint-ignore
@@ -260,6 +266,7 @@ export interface BuilderProgramState extends BuilderState, ReusableBuilderProgra
260266
/** Already seen program emit */
261267
seenProgramEmit: BuilderFileEmit | undefined;
262268
hasErrorsFromOldState?: boolean;
269+
packageJsonsFromOldState?: SortedReadonlyArray<string>;
263270
}
264271

265272
interface BuilderProgramStateWithDefinedProgram extends BuilderProgramState {
@@ -365,6 +372,7 @@ function createBuilderProgramState(
365372
canCopyEmitDiagnostics = false;
366373
}
367374
state.hasErrorsFromOldState = oldState!.hasErrors;
375+
state.packageJsonsFromOldState = oldState!.packageJsons;
368376
}
369377
else {
370378
// We arent using old state, so atleast emit buildInfo with current information
@@ -1139,6 +1147,7 @@ export interface IncrementalBuildInfoBase extends BuildInfo {
11391147
latestChangedDtsFile?: string | undefined;
11401148
errors: true | undefined;
11411149
checkPending: true | undefined;
1150+
packageJsons: string[] | undefined;
11421151
}
11431152

11441153
/** @internal */
@@ -1187,6 +1196,7 @@ export interface NonIncrementalBuildInfo extends BuildInfo {
11871196
root: readonly string[];
11881197
errors: true | undefined;
11891198
checkPending: true | undefined;
1199+
packageJsons: string[] | undefined;
11901200
}
11911201

11921202
function isNonIncrementalBuildInfo(info: BuildInfo): info is NonIncrementalBuildInfo {
@@ -1217,22 +1227,44 @@ function ensureHasErrorsForState(state: BuilderProgramStateWithDefinedProgram) {
12171227
}
12181228
}
12191229

1230+
function ensurePackageJsonsForState(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost) {
1231+
if (state.packageJsons !== undefined) return;
1232+
let packageJsons: string[] = [];
1233+
if (state.program.getCompilerOptions().configFilePath) {
1234+
const internalMap = state.program.getModuleResolutionCache()?.getPackageJsonInfoCache().getInternalMap();
1235+
if (internalMap) {
1236+
internalMap.forEach(value => {
1237+
if (isPackageJsonInfo(value)) {
1238+
let path = combinePaths(value.packageDirectory, "package.json");
1239+
if (host.realpath) {
1240+
path = host.realpath(path);
1241+
}
1242+
packageJsons.push(path);
1243+
}
1244+
});
1245+
}
1246+
}
1247+
state.packageJsons = sortAndDeduplicate(packageJsons);
1248+
}
1249+
12201250
function hasSyntaxOrGlobalErrors(state: BuilderProgramStateWithDefinedProgram) {
12211251
return !!state.program.getConfigFileParsingDiagnostics().length ||
12221252
!!state.program.getSyntacticDiagnostics().length ||
12231253
!!state.program.getOptionsDiagnostics().length ||
12241254
!!state.program.getGlobalDiagnostics().length;
12251255
}
12261256

1227-
function getBuildInfoEmitPending(state: BuilderProgramStateWithDefinedProgram) {
1257+
function getBuildInfoEmitPending(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost) {
12281258
ensureHasErrorsForState(state);
1229-
return state.buildInfoEmitPending ??= !!state.hasErrorsFromOldState !== !!state.hasErrors;
1259+
ensurePackageJsonsForState(state, host);
1260+
return state.buildInfoEmitPending ??= !!state.hasErrorsFromOldState !== !!state.hasErrors ||
1261+
!arrayIsEqualTo(state.packageJsons, state.packageJsonsFromOldState);
12301262
}
12311263

12321264
/**
12331265
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
12341266
*/
1235-
function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
1267+
function getBuildInfo(state: BuilderProgramStateWithDefinedProgram, host: BuilderProgramHost): BuildInfo {
12361268
const currentDirectory = state.program.getCurrentDirectory();
12371269
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
12381270
// 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
@@ -1241,11 +1273,13 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
12411273
const fileNameToFileId = new Map<string, IncrementalBuildInfoFileId>();
12421274
const rootFileNames = new Set(state.program.getRootFileNames().map(f => toPath(f, currentDirectory, state.program.getCanonicalFileName)));
12431275
ensureHasErrorsForState(state);
1276+
ensurePackageJsonsForState(state, host);
12441277
if (!isIncrementalCompilation(state.compilerOptions)) {
12451278
const buildInfo: NonIncrementalBuildInfo = {
12461279
root: arrayFrom(rootFileNames, r => relativeToBuildInfo(r)),
12471280
errors: state.hasErrors ? true : undefined,
12481281
checkPending: state.checkPending,
1282+
packageJsons: toPackageJsons(),
12491283
version,
12501284
};
12511285
return buildInfo;
@@ -1281,6 +1315,7 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
12811315
state.programEmitPending, // Actual value
12821316
errors: state.hasErrors ? true : undefined,
12831317
checkPending: state.checkPending,
1318+
packageJsons: toPackageJsons(),
12841319
version,
12851320
};
12861321
return buildInfo;
@@ -1373,6 +1408,7 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
13731408
latestChangedDtsFile,
13741409
errors: state.hasErrors ? true : undefined,
13751410
checkPending: state.checkPending,
1411+
packageJsons: toPackageJsons(),
13761412
version,
13771413
};
13781414
return buildInfo;
@@ -1564,6 +1600,10 @@ function getBuildInfo(state: BuilderProgramStateWithDefinedProgram): BuildInfo {
15641600
}
15651601
return changeFileSet;
15661602
}
1603+
1604+
function toPackageJsons() {
1605+
return state.packageJsons?.length ? state.packageJsons.map(relativeToBuildInfo) : undefined;
1606+
}
15671607
}
15681608

15691609
/** @internal */
@@ -1683,7 +1723,7 @@ export function createBuilderProgram(
16831723
}
16841724

16851725
const state = createBuilderProgramState(newProgram, oldState);
1686-
newProgram.getBuildInfo = () => getBuildInfo(toBuilderProgramStateWithDefinedProgram(state));
1726+
newProgram.getBuildInfo = () => getBuildInfo(toBuilderProgramStateWithDefinedProgram(state), host);
16871727

16881728
// To ensure that we arent storing any references to old program or new program without state
16891729
newProgram = undefined!;
@@ -1722,7 +1762,7 @@ export function createBuilderProgram(
17221762
cancellationToken: CancellationToken | undefined,
17231763
): EmitResult {
17241764
Debug.assert(isBuilderProgramStateWithDefinedProgram(state));
1725-
if (getBuildInfoEmitPending(state)) {
1765+
if (getBuildInfoEmitPending(state, host)) {
17261766
const result = state.program.emitBuildInfo(
17271767
writeFile || maybeBind(host, host.writeFile),
17281768
cancellationToken,
@@ -1809,7 +1849,7 @@ export function createBuilderProgram(
18091849

18101850
if (!affected) {
18111851
// Emit buildinfo if pending
1812-
if (isForDtsErrors || !getBuildInfoEmitPending(state)) return undefined;
1852+
if (isForDtsErrors || !getBuildInfoEmitPending(state, host)) return undefined;
18131853
const affected = state.program;
18141854
const result = affected.emitBuildInfo(
18151855
writeFile || maybeBind(host, host.writeFile),
@@ -2282,6 +2322,7 @@ export function createBuilderProgramUsingIncrementalBuildInfo(
22822322
programEmitPending: buildInfo.pendingEmit === undefined ? undefined : toProgramEmitPending(buildInfo.pendingEmit, buildInfo.options),
22832323
hasErrors: buildInfo.errors,
22842324
checkPending: buildInfo.checkPending,
2325+
packageJsons: toPackageJsons(),
22852326
};
22862327
}
22872328
else {
@@ -2320,6 +2361,7 @@ export function createBuilderProgramUsingIncrementalBuildInfo(
23202361
emitSignatures: emitSignatures?.size ? emitSignatures : undefined,
23212362
hasErrors: buildInfo.errors,
23222363
checkPending: buildInfo.checkPending,
2364+
packageJsons: toPackageJsons(),
23232365
};
23242366
}
23252367

@@ -2389,6 +2431,10 @@ export function createBuilderProgramUsingIncrementalBuildInfo(
23892431
function toPerFileEmitDiagnostics(diagnostics: readonly IncrementalBuildInfoEmitDiagnostic[] | undefined): Map<Path, readonly ReusableDiagnostic[]> | undefined {
23902432
return diagnostics && arrayToMap(diagnostics, value => toFilePath(value[0]), value => value[1]);
23912433
}
2434+
2435+
function toPackageJsons() {
2436+
return buildInfo.packageJsons?.map(toAbsolutePath) as unknown as SortedReadonlyArray<string> ?? emptyArray;
2437+
}
23922438
}
23932439

23942440
/** @internal */

src/compiler/builderPublic.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface BuilderProgramHost {
2929
* this callback if present would be used to write files
3030
*/
3131
writeFile?: WriteFileCallback;
32+
realpath?(path: string): string;
3233
/**
3334
* Store information about the signature
3435
*

src/compiler/tsbuildPublic.ts

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -415,11 +415,11 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
415415
readonly allWatchedInputFiles: Map<ResolvedConfigFilePath, Map<string, FileWatcher>>;
416416
readonly allWatchedConfigFiles: Map<ResolvedConfigFilePath, FileWatcher>;
417417
readonly allWatchedExtendedConfigFiles: Map<Path, SharedExtendedConfigFileWatcher<ResolvedConfigFilePath>>;
418-
readonly allWatchedPackageJsonFiles: Map<ResolvedConfigFilePath, Map<Path, FileWatcher>>;
418+
readonly allWatchedPackageJsonFiles: Map<ResolvedConfigFilePath, Map<string, FileWatcher>>;
419419
readonly filesWatched: Map<Path, FileWatcherWithModifiedTime | Date>;
420420
readonly outputTimeStamps: Map<ResolvedConfigFilePath, Map<Path, Date>>;
421421

422-
readonly lastCachedPackageJsonLookups: Map<ResolvedConfigFilePath, Set<string> | undefined>;
422+
readonly allWatchedPackageJsons: Map<ResolvedConfigFilePath, Set<string> | undefined>;
423423

424424
timerToBuildInvalidatedProject: any;
425425
reportFileChangeDetected: boolean;
@@ -539,8 +539,7 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
539539
allWatchedExtendedConfigFiles: new Map(),
540540
allWatchedPackageJsonFiles: new Map(),
541541
filesWatched: new Map(),
542-
543-
lastCachedPackageJsonLookups: new Map(),
542+
allWatchedPackageJsons: new Map(),
544543

545544
timerToBuildInvalidatedProject: undefined,
546545
reportFileChangeDetected: false,
@@ -679,7 +678,7 @@ function createStateBuildOrder<T extends BuilderProgram>(state: SolutionBuilderS
679678
mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete);
680679
mutateMapSkippingNewValues(state.buildInfoCache, currentProjects, noopOnDelete);
681680
mutateMapSkippingNewValues(state.outputTimeStamps, currentProjects, noopOnDelete);
682-
mutateMapSkippingNewValues(state.lastCachedPackageJsonLookups, currentProjects, noopOnDelete);
681+
mutateMapSkippingNewValues(state.allWatchedPackageJsons, currentProjects, noopOnDelete);
683682

684683
// Remove watches for the program no longer in the solution
685684
if (state.watch) {
@@ -1055,17 +1054,6 @@ function createBuildOrUpdateInvalidedProject<T extends BuilderProgram>(
10551054
config.projectReferences,
10561055
);
10571056
if (state.watch) {
1058-
const internalMap = state.moduleResolutionCache?.getPackageJsonInfoCache().getInternalMap();
1059-
state.lastCachedPackageJsonLookups.set(
1060-
projectPath,
1061-
internalMap && new Set(arrayFrom(
1062-
internalMap.values(),
1063-
data =>
1064-
state.host.realpath && (isPackageJsonInfo(data) || data.directoryExists) ?
1065-
state.host.realpath(combinePaths(data.packageDirectory, "package.json")) :
1066-
combinePaths(data.packageDirectory, "package.json"),
1067-
)),
1068-
);
10691057
state.builderPrograms.set(projectPath, program);
10701058
}
10711059
step++;
@@ -1770,12 +1758,17 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
17701758
if (extendedConfigStatus) return extendedConfigStatus;
17711759

17721760
// Check package file time
1773-
const packageJsonLookups = state.lastCachedPackageJsonLookups.get(resolvedPath);
1774-
const dependentPackageFileStatus = packageJsonLookups && forEachKey(
1775-
packageJsonLookups,
1776-
path => checkConfigFileUpToDateStatus(state, path, oldestOutputFileTime, oldestOutputFileName),
1761+
const packageJsonStatus = forEach(
1762+
(buildInfo as IncrementalBuildInfo | NonIncrementalBuildInfo).packageJsons,
1763+
packageJson =>
1764+
checkConfigFileUpToDateStatus(
1765+
state,
1766+
getNormalizedAbsolutePath(packageJson, getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()))),
1767+
oldestOutputFileTime,
1768+
oldestOutputFileName,
1769+
),
17771770
);
1778-
if (dependentPackageFileStatus) return dependentPackageFileStatus;
1771+
if (packageJsonStatus) return packageJsonStatus;
17791772

17801773
// Up to date
17811774
return {
@@ -2199,10 +2192,18 @@ function watchInputFiles<T extends BuilderProgram>(state: SolutionBuilderState<T
21992192
}
22002193

22012194
function watchPackageJsonFiles<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) {
2202-
if (!state.watch || !state.lastCachedPackageJsonLookups) return;
2195+
if (!state.watch) return;
2196+
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(parsed.options)!;
2197+
const buildInfoCacheEntry = getBuildInfoCacheEntry(state, buildInfoPath, resolvedPath);
22032198
mutateMap(
22042199
getOrCreateValueMapFromConfigFileMap(state.allWatchedPackageJsonFiles, resolvedPath),
2205-
state.lastCachedPackageJsonLookups.get(resolvedPath),
2200+
new Set(
2201+
buildInfoCacheEntry?.buildInfo ?
2202+
(buildInfoCacheEntry.buildInfo as IncrementalBuildInfo | NonIncrementalBuildInfo).packageJsons?.map(
2203+
f => getNormalizedAbsolutePath(f, getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, state.host.getCurrentDirectory()))),
2204+
) :
2205+
undefined,
2206+
),
22062207
{
22072208
createNewValue: input =>
22082209
watchFile(

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9672,6 +9672,7 @@ declare namespace ts {
96729672
* this callback if present would be used to write files
96739673
*/
96749674
writeFile?: WriteFileCallback;
9675+
realpath?(path: string): string;
96759676
}
96769677
/**
96779678
* Builder to manage the program state changes

tests/baselines/reference/tsbuild/declarationEmit/multiFile/reports-dts-generation-errors-with-incremental.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export const api = ky.extend({});
9090

9191

9292
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo]
93-
{"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"}
93+
{"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"}
9494

9595
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt]
9696
{
@@ -166,8 +166,12 @@ export const api = ky.extend({});
166166
]
167167
]
168168
],
169+
"packageJsons": [
170+
"./node_modules/ky/package.json",
171+
"./package.json"
172+
],
169173
"version": "FakeTSVersion",
170-
"size": 1345
174+
"size": 1412
171175
}
172176

173177

tests/baselines/reference/tsbuild/declarationEmit/multiFile/reports-dts-generation-errors.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,20 @@ export const api = ky.extend({});
9191

9292

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

9696
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt]
9797
{
9898
"root": [
9999
"./index.ts"
100100
],
101101
"errors": true,
102+
"packageJsons": [
103+
"./node_modules/ky/package.json",
104+
"./package.json"
105+
],
102106
"version": "FakeTSVersion",
103-
"size": 63
107+
"size": 130
104108
}
105109

106110

0 commit comments

Comments
 (0)