Skip to content

Commit

Permalink
Handle root files listed in project config from referenced project to…
Browse files Browse the repository at this point in the history
… be same as if they were included through import (#58560)
  • Loading branch information
sheetalkamat authored May 20, 2024
1 parent 79a8514 commit d84431e
Show file tree
Hide file tree
Showing 17 changed files with 6,870 additions and 91 deletions.
52 changes: 43 additions & 9 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
addRange,
AffectedFileResult,
append,
arrayFrom,
arrayToMap,
BuilderProgram,
Expand Down Expand Up @@ -990,10 +991,13 @@ export type ProgramBuildInfoRootStartEnd = [start: ProgramBuildInfoFileId, end:
*/
export type ProgramBuildInfoRoot = ProgramBuildInfoRootStartEnd | ProgramBuildInfoFileId;
/** @internal */
export type ProgramBuildInfoResolvedRoot = [resolved: ProgramBuildInfoFileId, root: ProgramBuildInfoFileId];
/** @internal */
export interface ProgramMultiFileEmitBuildInfo {
fileNames: readonly string[];
fileInfos: readonly ProgramMultiFileEmitBuildInfoFileInfo[];
root: readonly ProgramBuildInfoRoot[];
resolvedRoot: readonly ProgramBuildInfoResolvedRoot[] | undefined;
options: CompilerOptions | undefined;
fileIdsList: readonly (readonly ProgramBuildInfoFileId[])[] | undefined;
referencedMap: ProgramBuildInfoReferencedMap | undefined;
Expand Down Expand Up @@ -1023,6 +1027,7 @@ export interface ProgramBundleEmitBuildInfo {
fileNames: readonly string[];
fileInfos: readonly ProgramBundleEmitBuildInfoFileInfo[];
root: readonly ProgramBuildInfoRoot[];
resolvedRoot: readonly ProgramBuildInfoResolvedRoot[] | undefined;
options: CompilerOptions | undefined;
outSignature: EmitSignature | undefined;
latestChangedDtsFile: string | undefined;
Expand All @@ -1047,6 +1052,7 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
const latestChangedDtsFile = state.latestChangedDtsFile ? relativeToBuildInfoEnsuringAbsolutePath(state.latestChangedDtsFile) : undefined;
const fileNames: string[] = [];
const fileNameToFileId = new Map<string, ProgramBuildInfoFileId>();
const rootFileNames = new Set(state.program!.getRootFileNames().map(f => toPath(f, currentDirectory, state.program!.getCanonicalFileName)));
const root: ProgramBuildInfoRoot[] = [];
if (state.compilerOptions.outFile) {
// Copy all fileInfo, version and impliedFormat
Expand All @@ -1063,6 +1069,7 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
fileNames,
fileInfos,
root,
resolvedRoot: toResolvedRoot(),
options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions),
outSignature: state.outSignature,
latestChangedDtsFile,
Expand Down Expand Up @@ -1090,7 +1097,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
if (!isJsonSourceFile(file) && sourceFileMayBeEmitted(file, state.program!)) {
const emitSignature = state.emitSignatures?.get(key);
if (emitSignature !== actualSignature) {
(emitSignatures ||= []).push(
emitSignatures = append(
emitSignatures,
emitSignature === undefined ?
fileId : // There is no emit, encode as false
// fileId, signature: emptyArray if signature only differs in dtsMap option than our own compilerOptions otherwise EmitSignature
Expand Down Expand Up @@ -1133,7 +1141,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
const file = state.program!.getSourceFileByPath(path);
if (!file || !sourceFileMayBeEmitted(file, state.program!)) continue;
const fileId = toFileId(path), pendingEmit = state.affectedFilesPendingEmit.get(path)!;
(affectedFilesPendingEmit ||= []).push(
affectedFilesPendingEmit = append(
affectedFilesPendingEmit,
pendingEmit === fullEmitForOptions ?
fileId : // Pending full emit per options
pendingEmit === BuilderFileEmit.Dts ?
Expand All @@ -1147,14 +1156,15 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
let changeFileSet: ProgramBuildInfoFileId[] | undefined;
if (state.changedFilesSet.size) {
for (const path of arrayFrom(state.changedFilesSet.keys()).sort(compareStringsCaseSensitive)) {
(changeFileSet ||= []).push(toFileId(path));
changeFileSet = append(changeFileSet, toFileId(path));
}
}
const emitDiagnosticsPerFile = convertToProgramBuildInfoDiagnostics(state.emitDiagnosticsPerFile);
const program: ProgramMultiFileEmitBuildInfo = {
fileNames,
fileInfos,
root,
resolvedRoot: toResolvedRoot(),
options: convertToProgramBuildInfoCompilerOptions(state.compilerOptions),
fileIdsList,
referencedMap,
Expand Down Expand Up @@ -1189,8 +1199,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
const key = fileIds.join();
let fileIdListId = fileNamesToFileIdListId?.get(key);
if (fileIdListId === undefined) {
(fileIdsList ||= []).push(fileIds);
(fileNamesToFileIdListId ||= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId);
fileIdsList = append(fileIdsList, fileIds);
(fileNamesToFileIdListId ??= new Map()).set(key, fileIdListId = fileIdsList.length as ProgramBuildInfoFileIdListId);
}
return fileIdListId;
}
Expand All @@ -1214,6 +1224,17 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
return root.length = root.length - 1;
}

function toResolvedRoot(): ProgramBuildInfoResolvedRoot[] | undefined {
let result: ProgramBuildInfoResolvedRoot[] | undefined;
rootFileNames.forEach(path => {
const file = state.program!.getSourceFileByPath(path);
if (file && path !== file.resolvedPath) {
result = append(result, [toFileId(file.resolvedPath), toFileId(path)]);
}
});
return result;
}

/**
* @param optionKey key of CommandLineOption to use to determine if the option should be serialized in tsbuildinfo
*/
Expand Down Expand Up @@ -1253,7 +1274,8 @@ function getBuildInfo(state: BuilderProgramState): BuildInfo {
if (diagnostics) {
for (const key of arrayFrom(diagnostics.keys()).sort(compareStringsCaseSensitive)) {
const value = diagnostics.get(key)!;
(result ||= []).push(
result = append(
result,
value.length ?
[
toFileId(key),
Expand Down Expand Up @@ -1909,7 +1931,9 @@ export function getBuildInfoFileVersionMap(
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
const fileInfos = new Map<Path, string>();
let rootIndex = 0;
const roots: Path[] = [];
// Root name to resolved
const roots = new Map<Path, Path | undefined>();
const resolvedRoots = new Map(program.resolvedRoot);
program.fileInfos.forEach((fileInfo, index) => {
const path = toPath(program.fileNames[index], buildInfoDirectory, getCanonicalFileName);
const version = isString(fileInfo) ? fileInfo : fileInfo.version;
Expand All @@ -1919,17 +1943,27 @@ export function getBuildInfoFileVersionMap(
const fileId = (index + 1) as ProgramBuildInfoFileId;
if (isArray(current)) {
if (current[0] <= fileId && fileId <= current[1]) {
roots.push(path);
addRoot(fileId, path);
if (current[1] === fileId) rootIndex++;
}
}
else if (current === fileId) {
roots.push(path);
addRoot(fileId, path);
rootIndex++;
}
}
});
return { fileInfos, roots };

function addRoot(fileId: ProgramBuildInfoFileId, path: Path) {
const root = resolvedRoots.get(fileId);
if (root) {
roots.set(toPath(program.fileNames[root - 1], buildInfoDirectory, getCanonicalFileName), path);
}
else {
roots.set(path, undefined);
}
}
}

/** @internal */
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3765,7 +3765,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}

let redirectedPath: Path | undefined;
if (isReferencedFile(reason) && !useSourceOfProjectReferenceRedirect) {
if (!useSourceOfProjectReferenceRedirect) {
const redirectProject = getProjectReferenceRedirectProject(fileName);
if (redirectProject) {
if (redirectProject.commandLine.options.outFile) {
Expand Down
29 changes: 17 additions & 12 deletions src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
findIndex,
flattenDiagnosticMessageText,
forEach,
forEachEntry,
forEachKey,
ForegroundColorEscapeSequences,
formatColorAndReset,
Expand Down Expand Up @@ -1734,15 +1735,17 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
};
}

const inputPath = buildInfoProgram ? toPath(state, inputFile) : undefined;
// If an buildInfo is older than the newest input, we can stop checking
if (buildInfoTime && buildInfoTime < inputTime) {
let version: string | undefined;
let currentVersion: string | undefined;
if (buildInfoProgram) {
// Read files and see if they are same, read is anyways cached
if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host);
version = buildInfoVersionMap.fileInfos.get(toPath(state, inputFile));
const text = version ? state.readFileWithCache(inputFile) : undefined;
const resolvedInputPath = buildInfoVersionMap.roots.get(inputPath!);
version = buildInfoVersionMap.fileInfos.get(resolvedInputPath ?? inputPath!);
const text = version ? state.readFileWithCache(resolvedInputPath ?? inputFile) : undefined;
currentVersion = text !== undefined ? getSourceFileVersionAsHashFromText(host, text) : undefined;
if (version && version === currentVersion) pseudoInputUpToDate = true;
}
Expand All @@ -1761,20 +1764,22 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
newestInputFileTime = inputTime;
}

if (buildInfoProgram) seenRoots.add(toPath(state, inputFile));
if (buildInfoProgram) seenRoots.add(inputPath!);
}

if (buildInfoProgram) {
if (!buildInfoVersionMap) buildInfoVersionMap = getBuildInfoFileVersionMap(buildInfoProgram, buildInfoPath!, host);
for (const existingRoot of buildInfoVersionMap.roots) {
if (!seenRoots.has(existingRoot)) {
// File was root file when project was built but its not any more
return {
type: UpToDateStatusType.OutOfDateRoots,
buildInfoFile: buildInfoPath!,
inputFile: existingRoot,
};
}
const existingRoot = forEachEntry(
buildInfoVersionMap.roots,
// File was root file when project was built but its not any more
(_resolved, existingRoot) => !seenRoots.has(existingRoot) ? existingRoot : undefined,
);
if (existingRoot) {
return {
type: UpToDateStatusType.OutOfDateRoots,
buildInfoFile: buildInfoPath!,
inputFile: existingRoot,
};
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/testRunner/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export * from "./unittests/tsbuildWatch/programUpdates.js";
export * from "./unittests/tsbuildWatch/projectsBuilding.js";
export * from "./unittests/tsbuildWatch/publicApi.js";
export * from "./unittests/tsbuildWatch/reexport.js";
export * from "./unittests/tsbuildWatch/roots.js";
export * from "./unittests/tsbuildWatch/watchEnvironment.js";
export * from "./unittests/tsc/cancellationToken.js";
export * from "./unittests/tsc/composite.js";
Expand Down Expand Up @@ -197,6 +198,7 @@ export * from "./unittests/tsserver/projectReferenceCompileOnSave.js";
export * from "./unittests/tsserver/projectReferenceErrors.js";
export * from "./unittests/tsserver/projectReferences.js";
export * from "./unittests/tsserver/projectReferencesSourcemap.js";
export * from "./unittests/tsserver/projectRootFiles.js";
export * from "./unittests/tsserver/projects.js";
export * from "./unittests/tsserver/projectsWithReferences.js";
export * from "./unittests/tsserver/refactors.js";
Expand Down
17 changes: 15 additions & 2 deletions src/testRunner/unittests/helpers/baseline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,16 @@ export type ReadableProgramBuildInfoFileInfo<T> = Omit<ts.BuilderState.FileInfo,
export type ReadableProgramBuildInfoRoot =
| [original: ts.ProgramBuildInfoFileId, readable: string]
| [original: ts.ProgramBuildInfoRootStartEnd, readable: readonly string[]];
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo, "fileIdsList" | "fileInfos" | "root" | "referencedMap" | "semanticDiagnosticsPerFile" | "emitDiagnosticsPerFile" | "affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"> & {

export type ReadableProgramBuildInfoResolvedRoot = [
original: ts.ProgramBuildInfoResolvedRoot,
readable: [resolved: string, root: string],
];
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo, "fileIdsList" | "fileInfos" | "root" | "resolvedRoot" | "referencedMap" | "semanticDiagnosticsPerFile" | "emitDiagnosticsPerFile" | "affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"> & {
fileNamesList: readonly (readonly string[])[] | undefined;
fileInfos: ts.MapLike<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo>>;
root: readonly ReadableProgramBuildInfoRoot[];
resolvedRoot: readonly ReadableProgramBuildInfoResolvedRoot[] | undefined;
referencedMap: ts.MapLike<string[]> | undefined;
semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
emitDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
Expand All @@ -153,9 +159,10 @@ export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmit
emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined;
};
export type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ts.ProgramBuildInfoBundlePendingEmit];
export type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "root" | "pendingEmit"> & {
export type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "root" | "resolvedRoot" | "pendingEmit"> & {
fileInfos: ts.MapLike<string | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>>;
root: readonly ReadableProgramBuildInfoRoot[];
resolvedRoot: readonly ReadableProgramBuildInfoResolvedRoot[] | undefined;
pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined;
};

Expand All @@ -180,6 +187,7 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
...buildInfo.program,
fileInfos,
root: buildInfo.program.root.map(toReadableProgramBuildInfoRoot),
resolvedRoot: buildInfo.program.resolvedRoot?.map(toReadableProgramBuildInfoResolvedRoot),
pendingEmit: pendingEmit === undefined ?
undefined :
[
Expand All @@ -198,6 +206,7 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
fileNamesList,
fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!,
root: buildInfo.program.root.map(toReadableProgramBuildInfoRoot),
resolvedRoot: buildInfo.program.resolvedRoot?.map(toReadableProgramBuildInfoResolvedRoot),
options: buildInfo.program.options,
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
semanticDiagnosticsPerFile: toReadableProgramBuildInfoDiagnosticsPerFile(buildInfo.program.semanticDiagnosticsPerFile),
Expand Down Expand Up @@ -245,6 +254,10 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
return [original, readable];
}

function toReadableProgramBuildInfoResolvedRoot(original: ts.ProgramBuildInfoResolvedRoot): ReadableProgramBuildInfoResolvedRoot {
return [original, [toFileName(original[0]), toFileName(original[1])]];
}

function toMapOfReferencedSet(referenceMap: ts.ProgramBuildInfoReferencedMap | undefined): ts.MapLike<string[]> | undefined {
if (!referenceMap) return undefined;
const result: ts.MapLike<string[]> = {};
Expand Down
66 changes: 66 additions & 0 deletions src/testRunner/unittests/helpers/projectRoots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { dedent } from "../../_namespaces/Utils.js";
import { jsonToReadableText } from "../helpers.js";
import {
FsContents,
libContent,
} from "./contents.js";
import { libFile } from "./virtualFileSystemWithWatch.js";

function getFsContentsForRootsFromReferencedProject(serverFirst: boolean): FsContents {
return {
"/home/src/workspaces/tsconfig.json": jsonToReadableText({
compilerOptions: {
composite: true,
},
references: [
{ path: "projects/server" },
{ path: "projects/shared" },
],
}),
"/home/src/workspaces/projects/shared/src/myClass.ts": `export class MyClass { }`,
"/home/src/workspaces/projects/shared/src/logging.ts": dedent`
export function log(str: string) {
console.log(str);
}
`,
"/home/src/workspaces/projects/shared/src/random.ts": dedent`
export function randomFn(str: string) {
console.log(str);
}
`,
"/home/src/workspaces/projects/shared/tsconfig.json": jsonToReadableText({
extends: "../../tsconfig.json",
compilerOptions: {
outDir: "./dist",
},
include: ["src/**/*.ts"],
}),
"/home/src/workspaces/projects/server/src/server.ts": dedent`
import { MyClass } from ':shared/myClass.js';
console.log('Hello, world!');
`,
"/home/src/workspaces/projects/server/tsconfig.json": jsonToReadableText({
extends: "../../tsconfig.json",
compilerOptions: {
baseUrl: "./src",
rootDir: "..",
outDir: "./dist",
paths: {
":shared/*": ["../../shared/src/*"],
},
},
include: serverFirst ?
["src/**/*.ts", "../shared/src/**/*.ts"] :
["../shared/src/**/*.ts", "src/**/*.ts"],
references: [
{ path: "../shared" },
],
}),
[libFile.path]: libContent,
};
}

export function forEachScenarioForRootsFromReferencedProject(action: (subScenario: string, getFsContents: () => FsContents) => void) {
action("when root file is from referenced project", () => getFsContentsForRootsFromReferencedProject(/*serverFirst*/ true));
action("when root file is from referenced project and shared is first", () => getFsContentsForRootsFromReferencedProject(/*serverFirst*/ false));
}
Loading

0 comments on commit d84431e

Please sign in to comment.