Skip to content

Commit aac961e

Browse files
committed
Builder to use project reference redirects to output in the dependencies instead of source files
1 parent e710645 commit aac961e

File tree

7 files changed

+116
-14
lines changed

7 files changed

+116
-14
lines changed

src/compiler/builderState.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ namespace ts.BuilderState {
8888
function getReferencedFileFromImportedModuleSymbol(symbol: Symbol) {
8989
if (symbol.declarations && symbol.declarations[0]) {
9090
const declarationSourceFile = getSourceFileOfNode(symbol.declarations[0]);
91-
return declarationSourceFile && declarationSourceFile.path;
91+
return declarationSourceFile && (declarationSourceFile.resolvedPath || declarationSourceFile.path);
9292
}
9393
}
9494

@@ -100,6 +100,13 @@ namespace ts.BuilderState {
100100
return symbol && getReferencedFileFromImportedModuleSymbol(symbol);
101101
}
102102

103+
/**
104+
* Gets the path to reference file from file name, it could be resolvedPath if present otherwise path
105+
*/
106+
function getReferencedFileFromFileName(program: Program, fileName: string, sourceFileDirectory: Path, getCanonicalFileName: GetCanonicalFileName): Path {
107+
return toPath(program.getProjectReferenceRedirect(fileName) || fileName, sourceFileDirectory, getCanonicalFileName);
108+
}
109+
103110
/**
104111
* Gets the referenced files for a file from the program with values for the keys as referenced file's path to be true
105112
*/
@@ -123,7 +130,7 @@ namespace ts.BuilderState {
123130
// Handle triple slash references
124131
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
125132
for (const referencedFile of sourceFile.referencedFiles) {
126-
const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
133+
const referencedPath = getReferencedFileFromFileName(program, referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
127134
addReferencedFile(referencedPath);
128135
}
129136
}
@@ -136,7 +143,7 @@ namespace ts.BuilderState {
136143
}
137144

138145
const fileName = resolvedTypeReferenceDirective.resolvedFileName!; // TODO: GH#18217
139-
const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
146+
const typeFilePath = getReferencedFileFromFileName(program, fileName, sourceFileDirectory, getCanonicalFileName);
140147
addReferencedFile(typeFilePath);
141148
});
142149
}

src/compiler/program.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,8 @@ namespace ts {
758758
getConfigFileParsingDiagnostics,
759759
getResolvedModuleWithFailedLookupLocationsFromCache,
760760
getProjectReferences,
761-
getResolvedProjectReferences
761+
getResolvedProjectReferences,
762+
getProjectReferenceRedirect
762763
};
763764

764765
verifyCompilerOptions();

src/compiler/tsbuild.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ namespace ts {
862862
if (buildProject) {
863863
buildSingleInvalidatedProject(buildProject.project, buildProject.reloadLevel);
864864
if (hasPendingInvalidatedProjects()) {
865-
if (!timerToBuildInvalidatedProject) {
865+
if (options.watch && !timerToBuildInvalidatedProject) {
866866
scheduleBuildInvalidatedProject();
867867
}
868868
}

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2551,6 +2551,11 @@ namespace ts {
25512551
fileName: string;
25522552
/* @internal */ path: Path;
25532553
text: string;
2554+
/** Resolved path can be different from path property,
2555+
* when file is included through project reference is mapped to its output instead of source
2556+
* in that case resolvedPath = path to output file
2557+
* path = input file's path
2558+
*/
25542559
/* @internal */ resolvedPath: Path;
25552560

25562561
/**
@@ -2819,6 +2824,7 @@ namespace ts {
28192824

28202825
getProjectReferences(): ReadonlyArray<ProjectReference> | undefined;
28212826
getResolvedProjectReferences(): (ResolvedProjectReference | undefined)[] | undefined;
2827+
/*@internal*/ getProjectReferenceRedirect(fileName: string): string | undefined;
28222828
}
28232829

28242830
/* @internal */

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,12 @@ interface Array<T> {}`
934934
const folder = this.fs.get(base) as FsFolder;
935935
Debug.assert(isFsFolder(folder));
936936

937-
this.addFileOrFolderInFolder(folder, file);
937+
if (!this.fs.has(file.path)) {
938+
this.addFileOrFolderInFolder(folder, file);
939+
}
940+
else {
941+
this.modifyFile(path, content);
942+
}
938943
}
939944

940945
write(message: string) {

src/testRunner/unittests/tsbuildWatchMode.ts

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ namespace ts.tscWatch {
9494
const ui = subProjectFiles(SubProject.ui);
9595
const allFiles: ReadonlyArray<File> = [libFile, ...core, ...logic, ...tests, ...ui];
9696
const testProjectExpectedWatchedFiles = [core[0], core[1], core[2], ...logic, ...tests].map(f => f.path);
97+
const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
9798

9899
function createSolutionInWatchMode(allFiles: ReadonlyArray<File>, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) {
99100
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
@@ -110,7 +111,7 @@ namespace ts.tscWatch {
110111
function verifyWatches(host: WatchedSystem) {
111112
checkWatchedFiles(host, testProjectExpectedWatchedFiles);
112113
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
113-
checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true);
114+
checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
114115
}
115116

116117
it("creates solution in watch mode", () => {
@@ -161,7 +162,7 @@ namespace ts.tscWatch {
161162
function verifyWatches() {
162163
checkWatchedFiles(host, additionalFiles ? testProjectExpectedWatchedFiles.concat(newFile.path) : testProjectExpectedWatchedFiles);
163164
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
164-
checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true);
165+
checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
165166
}
166167
}
167168

@@ -347,7 +348,7 @@ function myFunc() { return 100; }`);
347348
function verifyWatches() {
348349
checkWatchedFiles(host, projectFiles.map(f => f.path));
349350
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
350-
checkWatchedDirectories(host, [projectPath(SubProject.core), projectPath(SubProject.logic)], /*recursive*/ true);
351+
checkWatchedDirectories(host, testProjectExpectedWatchedDirectoriesRecursive, /*recursive*/ true);
351352
}
352353
});
353354

@@ -389,12 +390,87 @@ let x: string = 10;`);
389390
});
390391
});
391392

392-
it("tsc-watch works with project references", () => {
393-
// Build the composite project
394-
const host = createSolutionInWatchMode(allFiles);
393+
describe("tsc-watch works with project references", () => {
394+
const coreIndexDts = projectFilePath(SubProject.core, "index.d.ts");
395+
const coreAnotherModuleDts = projectFilePath(SubProject.core, "anotherModule.d.ts");
396+
const logicIndexDts = projectFilePath(SubProject.logic, "index.d.ts");
397+
const expectedWatchedFiles = [core[0], logic[0], ...tests, libFile].map(f => f.path).concat(coreIndexDts, coreAnotherModuleDts, logicIndexDts);
398+
const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
399+
400+
function createSolution() {
401+
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
402+
const solutionBuilder = createSolutionBuilder(host, [`${project}/${SubProject.tests}`], {});
403+
return { host, solutionBuilder };
404+
}
395405

396-
createWatchOfConfigFile(tests[0].path, host);
397-
checkOutputErrorsInitial(host, emptyArray);
406+
function createBuiltSolution() {
407+
const result = createSolution();
408+
const { host, solutionBuilder } = result;
409+
solutionBuilder.buildAllProjects();
410+
const outputFileStamps = getOutputFileStamps(host);
411+
for (const stamp of outputFileStamps) {
412+
assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
413+
}
414+
return result;
415+
}
416+
417+
function verifyWatches(host: WatchedSystem) {
418+
checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
419+
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
420+
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true);
421+
}
422+
423+
function createSolutionAndWatchMode() {
424+
// Build the composite project
425+
const { host, solutionBuilder } = createBuiltSolution();
426+
427+
// Build in watch mode
428+
const watch = createWatchOfConfigFileReturningBuilder(tests[0].path, host);
429+
checkOutputErrorsInitial(host, emptyArray);
430+
431+
return { host, solutionBuilder, watch };
432+
}
433+
434+
function verifyDependencies(watch: () => BuilderProgram, filePath: string, expected: ReadonlyArray<string>) {
435+
checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!).map(f => f.toLocaleLowerCase()), expected);
436+
}
437+
438+
describe("invoking when references are already built", () => {
439+
it("verifies dependencies and watches", () => {
440+
const { host, watch } = createSolutionAndWatchMode();
441+
442+
verifyWatches(host);
443+
verifyDependencies(watch, coreIndexDts, [coreIndexDts]);
444+
verifyDependencies(watch, coreAnotherModuleDts, [coreAnotherModuleDts]);
445+
verifyDependencies(watch, logicIndexDts, [logicIndexDts, coreAnotherModuleDts]);
446+
verifyDependencies(watch, tests[1].path, [tests[1].path, coreAnotherModuleDts, logicIndexDts, coreAnotherModuleDts]);
447+
});
448+
449+
it("local edit in ts file, result in watch compilation because logic.d.ts is written", () => {
450+
const { host, solutionBuilder } = createSolutionAndWatchMode();
451+
host.writeFile(logic[1].path, `${logic[1].content}
452+
function foo() {
453+
}`);
454+
solutionBuilder.invalidateProject(`${project}/${SubProject.logic}`);
455+
solutionBuilder.buildInvalidatedProject();
456+
457+
host.checkTimeoutQueueLengthAndRun(1); // not ideal, but currently because of d.ts but no new file is written
458+
checkOutputErrorsIncremental(host, emptyArray);
459+
});
460+
461+
it("non local edit in ts file, rebuilds in watch compilation", () => {
462+
const { host, solutionBuilder } = createSolutionAndWatchMode();
463+
host.writeFile(logic[1].path, `${logic[1].content}
464+
export function gfoo() {
465+
}`);
466+
solutionBuilder.invalidateProject(logic[0].path);
467+
solutionBuilder.buildInvalidatedProject();
468+
469+
host.checkTimeoutQueueLengthAndRun(1);
470+
checkOutputErrorsIncremental(host, emptyArray);
471+
});
472+
473+
});
398474
});
399475
});
400476
}

src/testRunner/unittests/tscWatchMode.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ namespace ts.tscWatch {
2020
checkArray(`Program rootFileNames`, program.getRootFileNames(), expectedFiles);
2121
}
2222

23+
export function createWatchOfConfigFileReturningBuilder(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
24+
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
25+
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
26+
const watch = createWatchProgram(compilerHost);
27+
return () => watch.getCurrentProgram();
28+
}
29+
2330
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
2431
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, {}, host);
2532
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;

0 commit comments

Comments
 (0)