Skip to content

Commit d84431e

Browse files
authored
Handle root files listed in project config from referenced project to be same as if they were included through import (#58560)
1 parent 79a8514 commit d84431e

17 files changed

+6870
-91
lines changed

src/compiler/builder.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
addRange,
33
AffectedFileResult,
4+
append,
45
arrayFrom,
56
arrayToMap,
67
BuilderProgram,
@@ -990,10 +991,13 @@ export type ProgramBuildInfoRootStartEnd = [start: ProgramBuildInfoFileId, end:
990991
*/
991992
export type ProgramBuildInfoRoot = ProgramBuildInfoRootStartEnd | ProgramBuildInfoFileId;
992993
/** @internal */
994+
export type ProgramBuildInfoResolvedRoot = [resolved: ProgramBuildInfoFileId, root: ProgramBuildInfoFileId];
995+
/** @internal */
993996
export interface ProgramMultiFileEmitBuildInfo {
994997
fileNames: readonly string[];
995998
fileInfos: readonly ProgramMultiFileEmitBuildInfoFileInfo[];
996999
root: readonly ProgramBuildInfoRoot[];
1000+
resolvedRoot: readonly ProgramBuildInfoResolvedRoot[] | undefined;
9971001
options: CompilerOptions | undefined;
9981002
fileIdsList: readonly (readonly ProgramBuildInfoFileId[])[] | undefined;
9991003
referencedMap: ProgramBuildInfoReferencedMap | undefined;
@@ -1023,6 +1027,7 @@ export interface ProgramBundleEmitBuildInfo {
10231027
fileNames: readonly string[];
10241028
fileInfos: readonly ProgramBundleEmitBuildInfoFileInfo[];
10251029
root: readonly ProgramBuildInfoRoot[];
1030+
resolvedRoot: readonly ProgramBuildInfoResolvedRoot[] | undefined;
10261031
options: CompilerOptions | undefined;
10271032
outSignature: EmitSignature | undefined;
10281033
latestChangedDtsFile: string | undefined;
@@ -1047,6 +1052,7 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
10471052
const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined;
10481053
const fileNames: string[] = [];
10491054
const fileNameToFileId = new Map<string, ProgramBuildInfoFileId>();
1055+
const rootFileNames = new Set(state.program!.getRootFileNames().map(f => toPath(f, currentDirectory, state.program!.getCanonicalFileName)));
10501056
const root: ProgramBuildInfoRoot[] = [];
10511057
if (state.compilerOptions.outFile) {
10521058
// Copy all fileInfo, version and impliedFormat
@@ -1063,6 +1069,7 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
10631069
fileNames,
10641070
fileInfos,
10651071
root,
1072+
resolvedRoot: toResolvedRoot(),
10661073
options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions),
10671074
outSignature: state.outSignature,
10681075
latestChangedDtsFile,
@@ -1090,7 +1097,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
10901097
if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program!)) {
10911098
const emitSignature = state.emitSignatures?.get(key);
10921099
if (emitSignature !== actualSignature) {
1093-
(emitSignatures ||= []).push(
1100+
emitSignatures = append(
1101+
emitSignatures,
10941102
emitSignature === undefined ?
10951103
fileId : // There is no emit, encode as false
10961104
// fileId, signature: emptyArray if signature only differs in dtsMap option than our own compilerOptions otherwise EmitSignature
@@ -1133,7 +1141,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
11331141
const file = state.program!.getSourceFileByPath(path);
11341142
if (!file || !sourceFileMayBeEmitted(file, state.program!)) continue;
11351143
const fileId = toFileId(path), pendingEmit = state.affectedFilesPendingEmit.get(path)!;
1136-
(affectedFilesPendingEmit ||= []).push(
1144+
affectedFilesPendingEmit = append(
1145+
affectedFilesPendingEmit,
11371146
pendingEmit === fullEmitForOptions ?
11381147
fileId : // Pending full emit per options
11391148
pendingEmit === BuilderFileEmit.Dts ?
@@ -1147,14 +1156,15 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
11471156
let changeFileSet: ProgramBuildInfoFileId[] | undefined;
11481157
if (state.changedFilesSet.size) {
11491158
for (const path of arrayFrom(state.changedFilesSet.keys()).sort(compareStringsCaseSensitive)) {
1150-
(changeFileSet ||= []).push(toFileId(path));
1159+
changeFileSet = append(changeFileSet, toFileId(path));
11511160
}
11521161
}
11531162
const emitDiagnosticsPerFile = convertToProgramBuildInfoDiagnostics(state.emitDiagnosticsPerFile);
11541163
const program: ProgramMultiFileEmitBuildInfo = {
11551164
fileNames,
11561165
fileInfos,
11571166
root,
1167+
resolvedRoot: toResolvedRoot(),
11581168
options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions),
11591169
fileIdsList,
11601170
referencedMap,
@@ -1189,8 +1199,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
11891199
const key = fileIds.join();
11901200
let fileIdListId = fileNamesToFileIdListId?.get(key);
11911201
if (fileIdListId === undefined) {
1192-
(fileIdsList ||= []).push(fileIds);
1193-
(fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId);
1202+
fileIdsList = append(fileIdsList, fileIds);
1203+
(fileNamesToFileIdListId ??= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId);
11941204
}
11951205
return fileIdListId;
11961206
}
@@ -1214,6 +1224,17 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
12141224
return root.length = root.length - 1;
12151225
}
12161226

1227+
function toResolvedRoot(): ProgramBuildInfoResolvedRoot[] | undefined {
1228+
let result: ProgramBuildInfoResolvedRoot[] | undefined;
1229+
rootFileNames.forEach(path => {
1230+
const file = state.program!.getSourceFileByPath(path);
1231+
if (file && path !== file.resolvedPath) {
1232+
result = append(result, [toFileId(file.resolvedPath), toFileId(path)]);
1233+
}
1234+
});
1235+
return result;
1236+
}
1237+
12171238
/**
12181239
* @param optionKey key of CommandLineOption to use to determine if the option should be serialized in tsbuildinfo
12191240
*/
@@ -1253,7 +1274,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
12531274
if (diagnostics) {
12541275
for (const key of arrayFrom(diagnostics.keys()).sort(compareStringsCaseSensitive)) {
12551276
const value = diagnostics.get(key)!;
1256-
(result ||= []).push(
1277+
result = append(
1278+
result,
12571279
value.length ?
12581280
[
12591281
toFileId(key),
@@ -1909,7 +1931,9 @@ export function getBuildInfoFileVersionMap(
19091931
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
19101932
const fileInfos = new Map<Path, string>();
19111933
let rootIndex = 0;
1912-
const roots: Path[] = [];
1934+
// Root name to resolved
1935+
const roots = new Map<Path, Path | undefined>();
1936+
const resolvedRoots = new Map(program.resolvedRoot);
19131937
program.fileInfos.forEach((fileInfo, index) => {
19141938
const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName);
19151939
const version = isString(fileInfo) ? fileInfo : fileInfo.version;
@@ -1919,17 +1943,27 @@ export function getBuildInfoFileVersionMap(
19191943
const fileId = (index + 1) as ProgramBuildInfoFileId;
19201944
if (isArray(current)) {
19211945
if (current[0] <= fileId && fileId <= current[1]) {
1922-
roots.push(path);
1946+
addRoot(fileId, path);
19231947
if (current[1] === fileId) rootIndex++;
19241948
}
19251949
}
19261950
else if (current === fileId) {
1927-
roots.push(path);
1951+
addRoot(fileId, path);
19281952
rootIndex++;
19291953
}
19301954
}
19311955
});
19321956
return { fileInfos, roots };
1957+
1958+
function addRoot(fileId: ProgramBuildInfoFileId, path: Path) {
1959+
const root = resolvedRoots.get(fileId);
1960+
if (root) {
1961+
roots.set(toPath(program.fileNames[root - 1], buildInfoDirectory, getCanonicalFileName), path);
1962+
}
1963+
else {
1964+
roots.set(path, undefined);
1965+
}
1966+
}
19331967
}
19341968

19351969
/** @internal */

src/compiler/program.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3765,7 +3765,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
37653765
}
37663766

37673767
let redirectedPath: Path | undefined;
3768-
if (isReferencedFile(reason) && !useSourceOfProjectReferenceRedirect) {
3768+
if (!useSourceOfProjectReferenceRedirect) {
37693769
const redirectProject = getProjectReferenceRedirectProject(fileName);
37703770
if (redirectProject) {
37713771
if (redirectProject.commandLine.options.outFile) {

src/compiler/tsbuildPublic.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
findIndex,
4949
flattenDiagnosticMessageText,
5050
forEach,
51+
forEachEntry,
5152
forEachKey,
5253
ForegroundColorEscapeSequences,
5354
formatColorAndReset,
@@ -1734,15 +1735,17 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
17341735
};
17351736
}
17361737

1738+
const inputPath = buildInfoProgram ? toPath(state, inputFile) : undefined;
17371739
// If an buildInfo is older than the newest input, we can stop checking
17381740
if (buildInfoTime && buildInfoTime < inputTime) {
17391741
let version: string | undefined;
17401742
let currentVersion: string | undefined;
17411743
if (buildInfoProgram) {
17421744
// Read files and see if they are same, read is anyways cached
17431745
if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host);
1744-
version = buildInfoVersionMap.fileInfos.get(toPath(state, inputFile));
1745-
const text = version ? state.readFileWithCache(inputFile) : undefined;
1746+
const resolvedInputPath = buildInfoVersionMap.roots.get(inputPath!);
1747+
version = buildInfoVersionMap.fileInfos.get(resolvedInputPath ?? inputPath!);
1748+
const text = version ? state.readFileWithCache(resolvedInputPath ?? inputFile) : undefined;
17461749
currentVersion = text !== undefined ? getSourceFileVersionAsHashFromText(host, text) : undefined;
17471750
if (version && version === currentVersion) pseudoInputUpToDate = true;
17481751
}
@@ -1761,20 +1764,22 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
17611764
newestInputFileTime = inputTime;
17621765
}
17631766

1764-
if (buildInfoProgram) seenRoots.add(toPath(state, inputFile));
1767+
if (buildInfoProgram) seenRoots.add(inputPath!);
17651768
}
17661769

17671770
if (buildInfoProgram) {
17681771
if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host);
1769-
for (const existingRoot of buildInfoVersionMap.roots) {
1770-
if (!seenRoots.has(existingRoot)) {
1771-
// File was root file when project was built but its not any more
1772-
return {
1773-
type: UpToDateStatusType.OutOfDateRoots,
1774-
buildInfoFile: buildInfoPath!,
1775-
inputFile: existingRoot,
1776-
};
1777-
}
1772+
const existingRoot = forEachEntry(
1773+
buildInfoVersionMap.roots,
1774+
// File was root file when project was built but its not any more
1775+
(_resolved, existingRoot) => !seenRoots.has(existingRoot) ? existingRoot : undefined,
1776+
);
1777+
if (existingRoot) {
1778+
return {
1779+
type: UpToDateStatusType.OutOfDateRoots,
1780+
buildInfoFile: buildInfoPath!,
1781+
inputFile: existingRoot,
1782+
};
17781783
}
17791784
}
17801785

src/testRunner/tests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export * from "./unittests/tsbuildWatch/programUpdates.js";
110110
export * from "./unittests/tsbuildWatch/projectsBuilding.js";
111111
export * from "./unittests/tsbuildWatch/publicApi.js";
112112
export * from "./unittests/tsbuildWatch/reexport.js";
113+
export * from "./unittests/tsbuildWatch/roots.js";
113114
export * from "./unittests/tsbuildWatch/watchEnvironment.js";
114115
export * from "./unittests/tsc/cancellationToken.js";
115116
export * from "./unittests/tsc/composite.js";
@@ -197,6 +198,7 @@ export * from "./unittests/tsserver/projectReferenceCompileOnSave.js";
197198
export * from "./unittests/tsserver/projectReferenceErrors.js";
198199
export * from "./unittests/tsserver/projectReferences.js";
199200
export * from "./unittests/tsserver/projectReferencesSourcemap.js";
201+
export * from "./unittests/tsserver/projectRootFiles.js";
200202
export * from "./unittests/tsserver/projects.js";
201203
export * from "./unittests/tsserver/projectsWithReferences.js";
202204
export * from "./unittests/tsserver/refactors.js";

src/testRunner/unittests/helpers/baseline.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,16 @@ export type ReadableProgramBuildInfoFileInfo<T> = Omit<ts.BuilderState.FileInfo,
141141
export type ReadableProgramBuildInfoRoot =
142142
| [original: ts.ProgramBuildInfoFileId, readable: string]
143143
| [original: ts.ProgramBuildInfoRootStartEnd, readable: readonly string[]];
144-
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo, "fileIdsList" | "fileInfos" | "root" | "referencedMap" | "semanticDiagnosticsPerFile" | "emitDiagnosticsPerFile" | "affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"> & {
144+
145+
export type ReadableProgramBuildInfoResolvedRoot = [
146+
original: ts.ProgramBuildInfoResolvedRoot,
147+
readable: [resolved: string, root: string],
148+
];
149+
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo, "fileIdsList" | "fileInfos" | "root" | "resolvedRoot" | "referencedMap" | "semanticDiagnosticsPerFile" | "emitDiagnosticsPerFile" | "affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"> & {
145150
fileNamesList: readonly (readonly string[])[] | undefined;
146151
fileInfos: ts.MapLike<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo>>;
147152
root: readonly ReadableProgramBuildInfoRoot[];
153+
resolvedRoot: readonly ReadableProgramBuildInfoResolvedRoot[] | undefined;
148154
referencedMap: ts.MapLike<string[]> | undefined;
149155
semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
150156
emitDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
@@ -153,9 +159,10 @@ export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmit
153159
emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined;
154160
};
155161
export type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ts.ProgramBuildInfoBundlePendingEmit];
156-
export type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "root" | "pendingEmit"> & {
162+
export type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "root" | "resolvedRoot" | "pendingEmit"> & {
157163
fileInfos: ts.MapLike<string | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>>;
158164
root: readonly ReadableProgramBuildInfoRoot[];
165+
resolvedRoot: readonly ReadableProgramBuildInfoResolvedRoot[] | undefined;
159166
pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined;
160167
};
161168

@@ -180,6 +187,7 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
180187
...buildInfo.program,
181188
fileInfos,
182189
root: buildInfo.program.root.map(toReadableProgramBuildInfoRoot),
190+
resolvedRoot: buildInfo.program.resolvedRoot?.map(toReadableProgramBuildInfoResolvedRoot),
183191
pendingEmit: pendingEmit === undefined ?
184192
undefined :
185193
[
@@ -198,6 +206,7 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
198206
fileNamesList,
199207
fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!,
200208
root: buildInfo.program.root.map(toReadableProgramBuildInfoRoot),
209+
resolvedRoot: buildInfo.program.resolvedRoot?.map(toReadableProgramBuildInfoResolvedRoot),
201210
options: buildInfo.program.options,
202211
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
203212
semanticDiagnosticsPerFile: toReadableProgramBuildInfoDiagnosticsPerFile(buildInfo.program.semanticDiagnosticsPerFile),
@@ -245,6 +254,10 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
245254
return [original, readable];
246255
}
247256

257+
function toReadableProgramBuildInfoResolvedRoot(original: ts.ProgramBuildInfoResolvedRoot): ReadableProgramBuildInfoResolvedRoot {
258+
return [original, [toFileName(original[0]), toFileName(original[1])]];
259+
}
260+
248261
function toMapOfReferencedSet(referenceMap: ts.ProgramBuildInfoReferencedMap | undefined): ts.MapLike<string[]> | undefined {
249262
if (!referenceMap) return undefined;
250263
const result: ts.MapLike<string[]> = {};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { dedent } from "../../_namespaces/Utils.js";
2+
import { jsonToReadableText } from "../helpers.js";
3+
import {
4+
FsContents,
5+
libContent,
6+
} from "./contents.js";
7+
import { libFile } from "./virtualFileSystemWithWatch.js";
8+
9+
function getFsContentsForRootsFromReferencedProject(serverFirst: boolean): FsContents {
10+
return {
11+
"/home/src/workspaces/tsconfig.json": jsonToReadableText({
12+
compilerOptions: {
13+
composite: true,
14+
},
15+
references: [
16+
{ path: "projects/server" },
17+
{ path: "projects/shared" },
18+
],
19+
}),
20+
"/home/src/workspaces/projects/shared/src/myClass.ts": `export class MyClass { }`,
21+
"/home/src/workspaces/projects/shared/src/logging.ts": dedent`
22+
export function log(str: string) {
23+
console.log(str);
24+
}
25+
`,
26+
"/home/src/workspaces/projects/shared/src/random.ts": dedent`
27+
export function randomFn(str: string) {
28+
console.log(str);
29+
}
30+
`,
31+
"/home/src/workspaces/projects/shared/tsconfig.json": jsonToReadableText({
32+
extends: "../../tsconfig.json",
33+
compilerOptions: {
34+
outDir: "./dist",
35+
},
36+
include: ["src/**/*.ts"],
37+
}),
38+
"/home/src/workspaces/projects/server/src/server.ts": dedent`
39+
import { MyClass } from ':shared/myClass.js';
40+
console.log('Hello, world!');
41+
`,
42+
"/home/src/workspaces/projects/server/tsconfig.json": jsonToReadableText({
43+
extends: "../../tsconfig.json",
44+
compilerOptions: {
45+
baseUrl: "./src",
46+
rootDir: "..",
47+
outDir: "./dist",
48+
paths: {
49+
":shared/*": ["../../shared/src/*"],
50+
},
51+
},
52+
include: serverFirst ?
53+
["src/**/*.ts", "../shared/src/**/*.ts"] :
54+
["../shared/src/**/*.ts", "src/**/*.ts"],
55+
references: [
56+
{ path: "../shared" },
57+
],
58+
}),
59+
[libFile.path]: libContent,
60+
};
61+
}
62+
63+
export function forEachScenarioForRootsFromReferencedProject(action: (subScenario: string, getFsContents: () => FsContents) => void) {
64+
action("when root file is from referenced project", () => getFsContentsForRootsFromReferencedProject(/*serverFirst*/ true));
65+
action("when root file is from referenced project and shared is first", () => getFsContentsForRootsFromReferencedProject(/*serverFirst*/ false));
66+
}

0 commit comments

Comments
 (0)