Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle root files listed in project config from referenced project to be same as if they were included through import #58560

Merged
merged 4 commits into from
May 20, 2024
Merged
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
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