Skip to content

Commit b529d5b

Browse files
authored
Merge pull request #21149 from Microsoft/tweakRecompileConditions
Do not trigger resolution cache invalidation if watch is triggered by program file emit
2 parents b1bbed8 + 69bb5ea commit b529d5b

File tree

8 files changed

+88
-8
lines changed

8 files changed

+88
-8
lines changed

src/compiler/emitter.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ namespace ts {
1818
* If an array, the full list of source files to emit.
1919
* Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit.
2020
*/
21-
export function forEachEmittedFile(
22-
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => void,
21+
export function forEachEmittedFile<T>(
22+
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => T,
2323
sourceFilesOrTargetSourceFile?: SourceFile[] | SourceFile,
2424
emitOnlyDtsFiles?: boolean) {
2525

@@ -30,15 +30,21 @@ namespace ts {
3030
const jsFilePath = options.outFile || options.out;
3131
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
3232
const declarationFilePath = options.declaration ? removeFileExtension(jsFilePath) + Extension.Dts : "";
33-
action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles);
33+
const result = action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles);
34+
if (result) {
35+
return result;
36+
}
3437
}
3538
}
3639
else {
3740
for (const sourceFile of sourceFiles) {
3841
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options));
3942
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
4043
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
41-
action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles);
44+
const result = action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles);
45+
if (result) {
46+
return result;
47+
}
4248
}
4349
}
4450
}

src/compiler/program.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,8 @@ namespace ts {
667667
dropDiagnosticsProducingTypeChecker,
668668
getSourceFileFromReference,
669669
sourceFileToPackageName,
670-
redirectTargetsSet
670+
redirectTargetsSet,
671+
isEmittedFile
671672
};
672673

673674
verifyCompilerOptions();
@@ -2343,6 +2344,20 @@ namespace ts {
23432344
hasEmitBlockingDiagnostics.set(toPath(emitFileName), true);
23442345
programDiagnostics.add(diag);
23452346
}
2347+
2348+
function isEmittedFile(file: string) {
2349+
if (options.noEmit) {
2350+
return false;
2351+
}
2352+
2353+
return forEachEmittedFile(getEmitHost(), ({ jsFilePath, declarationFilePath }) =>
2354+
isSameFile(jsFilePath, file) ||
2355+
(declarationFilePath && isSameFile(declarationFilePath, file)));
2356+
}
2357+
2358+
function isSameFile(file1: string, file2: string) {
2359+
return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
2360+
}
23462361
}
23472362

23482363
/* @internal */

src/compiler/resolutionCache.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ namespace ts {
5252
getGlobalCache?(): string | undefined;
5353
writeLog(s: string): void;
5454
maxNumberOfFilesToIterateForInvalidation?: number;
55+
getCurrentProgram(): Program;
5556
}
5657

5758
interface DirectoryWatchesOfFailedLookup {
@@ -472,6 +473,11 @@ namespace ts {
472473
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
473474
}
474475

476+
// Ignore emits from the program
477+
if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectory)) {
478+
return;
479+
}
480+
475481
// If the files are added to project root or node_modules directory, always run through the invalidation process
476482
// Otherwise run through invalidation only if adding to the immediate directory
477483
if (!allFilesHaveInvalidatedResolution &&

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,6 +2659,8 @@ namespace ts {
26592659
/* @internal */ sourceFileToPackageName: Map<string>;
26602660
/** Set of all source files that some other source file redirects to. */
26612661
/* @internal */ redirectTargetsSet: Map<true>;
2662+
/** Is the file emitted file */
2663+
/* @internal */ isEmittedFile(file: string): boolean;
26622664
}
26632665

26642666
/* @internal */

src/compiler/watch.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ namespace ts {
2020
// Callbacks to do custom action before creating program and after creating program
2121
beforeCompile(compilerOptions: CompilerOptions): void;
2222
afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void;
23+
24+
// Only for testing
25+
maxNumberOfFilesToIterateForInvalidation?: number;
2326
}
2427

2528
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? {
@@ -301,6 +304,8 @@ namespace ts {
301304
hasChangedAutomaticTypeDirectiveNames = true;
302305
scheduleProgramUpdate();
303306
},
307+
maxNumberOfFilesToIterateForInvalidation: watchingHost.maxNumberOfFilesToIterateForInvalidation,
308+
getCurrentProgram,
304309
writeLog
305310
};
306311
// Cache for the module resolution
@@ -318,7 +323,11 @@ namespace ts {
318323
// Update the wild card directory watch
319324
watchConfigFileWildCardDirectories();
320325

321-
return () => program;
326+
return getCurrentProgram;
327+
328+
function getCurrentProgram() {
329+
return program;
330+
}
322331

323332
function synchronizeProgram() {
324333
writeLog(`Synchronizing program`);

src/compiler/watchUtilities.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ namespace ts {
8282
}
8383
}
8484

85+
export function isEmittedFileOfProgram(program: Program | undefined, file: string) {
86+
if (!program) {
87+
return false;
88+
}
89+
90+
return program.isEmittedFile(file);
91+
}
92+
8593
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
8694
return host.watchFile(file, cb);
8795
}

src/harness/unittests/tscWatchMode.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ namespace ts.tscWatch {
3030
return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic);
3131
}
3232

33-
function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) {
33+
function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
3434
const watchingSystemHost = createWatchingSystemHost(host);
35+
watchingSystemHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
3536
const configFileResult = parseConfigFile(configFilePath, watchingSystemHost);
3637
return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost);
3738
}
@@ -1067,6 +1068,36 @@ namespace ts.tscWatch {
10671068
assert.equal(nowErrors[0].start, intialErrors[0].start - configFileContentComment.length);
10681069
assert.equal(nowErrors[1].start, intialErrors[1].start - configFileContentComment.length);
10691070
});
1071+
1072+
it("should not trigger recompilation because of program emit", () => {
1073+
const proj = "/user/username/projects/myproject";
1074+
const file1: FileOrFolder = {
1075+
path: `${proj}/file1.ts`,
1076+
content: "export const c = 30;"
1077+
};
1078+
const file2: FileOrFolder = {
1079+
path: `${proj}/src/file2.ts`,
1080+
content: `import {c} from "file1"; export const d = 30;`
1081+
};
1082+
const tsconfig: FileOrFolder = {
1083+
path: `${proj}/tsconfig.json`,
1084+
content: JSON.stringify({
1085+
compilerOptions: {
1086+
module: "amd",
1087+
outDir: "build"
1088+
}
1089+
})
1090+
};
1091+
const host = createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: proj });
1092+
const watch = createWatchModeWithConfigFile(tsconfig.path, host, /*maxNumberOfFilesToIterateForInvalidation*/1);
1093+
checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]);
1094+
1095+
assert.isTrue(host.fileExists("build/file1.js"));
1096+
assert.isTrue(host.fileExists("build/src/file2.js"));
1097+
1098+
// This should be 0
1099+
host.checkTimeoutQueueLengthAndRun(0);
1100+
});
10701101
});
10711102

10721103
describe("tsc-watch emit with outFile or out setting", () => {

src/server/project.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,10 @@ namespace ts.server {
830830
return !hasChanges;
831831
}
832832

833-
833+
/* @internal */
834+
getCurrentProgram() {
835+
return this.program;
836+
}
834837

835838
protected removeExistingTypings(include: string[]): string[] {
836839
const existing = ts.getAutomaticTypeDirectiveNames(this.getCompilerOptions(), this.directoryStructureHost);

0 commit comments

Comments
 (0)