Skip to content

Do not trigger resolution cache invalidation if watch is triggered by program file emit #21149

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

Merged
merged 2 commits into from
Jan 12, 2018
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
14 changes: 10 additions & 4 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ namespace ts {
* If an array, the full list of source files to emit.
* Else, calls `getSourceFilesToEmit` with the (optional) target source file to determine the list of source files to emit.
*/
export function forEachEmittedFile(
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => void,
export function forEachEmittedFile<T>(
host: EmitHost, action: (emitFileNames: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle, emitOnlyDtsFiles: boolean) => T,
sourceFilesOrTargetSourceFile?: SourceFile[] | SourceFile,
emitOnlyDtsFiles?: boolean) {

Expand All @@ -30,15 +30,21 @@ namespace ts {
const jsFilePath = options.outFile || options.out;
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
const declarationFilePath = options.declaration ? removeFileExtension(jsFilePath) + Extension.Dts : "";
action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles);
const result = action({ jsFilePath, sourceMapFilePath, declarationFilePath }, createBundle(sourceFiles), emitOnlyDtsFiles);
if (result) {
return result;
}
}
}
else {
for (const sourceFile of sourceFiles) {
const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options));
const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options);
const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined;
action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles);
const result = action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles);
if (result) {
return result;
}
}
}
}
Expand Down
17 changes: 16 additions & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,8 @@ namespace ts {
dropDiagnosticsProducingTypeChecker,
getSourceFileFromReference,
sourceFileToPackageName,
redirectTargetsSet
redirectTargetsSet,
isEmittedFile
};

verifyCompilerOptions();
Expand Down Expand Up @@ -2343,6 +2344,20 @@ namespace ts {
hasEmitBlockingDiagnostics.set(toPath(emitFileName), true);
programDiagnostics.add(diag);
}

function isEmittedFile(file: string) {
if (options.noEmit) {
return false;
}

return forEachEmittedFile(getEmitHost(), ({ jsFilePath, declarationFilePath }) =>
isSameFile(jsFilePath, file) ||
(declarationFilePath && isSameFile(declarationFilePath, file)));
}

function isSameFile(file1: string, file2: string) {
return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo;
}
}

/* @internal */
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/resolutionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ namespace ts {
getGlobalCache?(): string | undefined;
writeLog(s: string): void;
maxNumberOfFilesToIterateForInvalidation?: number;
getCurrentProgram(): Program;
}

interface DirectoryWatchesOfFailedLookup {
Expand Down Expand Up @@ -472,6 +473,11 @@ namespace ts {
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}

// Ignore emits from the program
if (isEmittedFileOfProgram(resolutionHost.getCurrentProgram(), fileOrDirectory)) {
return;
}

// If the files are added to project root or node_modules directory, always run through the invalidation process
// Otherwise run through invalidation only if adding to the immediate directory
if (!allFilesHaveInvalidatedResolution &&
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2659,6 +2659,8 @@ namespace ts {
/* @internal */ sourceFileToPackageName: Map<string>;
/** Set of all source files that some other source file redirects to. */
/* @internal */ redirectTargetsSet: Map<true>;
/** Is the file emitted file */
/* @internal */ isEmittedFile(file: string): boolean;
}

/* @internal */
Expand Down
11 changes: 10 additions & 1 deletion src/compiler/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ namespace ts {
// Callbacks to do custom action before creating program and after creating program
beforeCompile(compilerOptions: CompilerOptions): void;
afterCompile(host: DirectoryStructureHost, program: Program, builder: Builder): void;

// Only for testing
maxNumberOfFilesToIterateForInvalidation?: number;
}

const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? {
Expand Down Expand Up @@ -301,6 +304,8 @@ namespace ts {
hasChangedAutomaticTypeDirectiveNames = true;
scheduleProgramUpdate();
},
maxNumberOfFilesToIterateForInvalidation: watchingHost.maxNumberOfFilesToIterateForInvalidation,
getCurrentProgram,
writeLog
};
// Cache for the module resolution
Expand All @@ -318,7 +323,11 @@ namespace ts {
// Update the wild card directory watch
watchConfigFileWildCardDirectories();

return () => program;
return getCurrentProgram;

function getCurrentProgram() {
return program;
}

function synchronizeProgram() {
writeLog(`Synchronizing program`);
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/watchUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ namespace ts {
}
}

export function isEmittedFileOfProgram(program: Program | undefined, file: string) {
if (!program) {
return false;
}

return program.isEmittedFile(file);
}

export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
return host.watchFile(file, cb);
}
Expand Down
33 changes: 32 additions & 1 deletion src/harness/unittests/tscWatchMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ namespace ts.tscWatch {
return ts.parseConfigFile(configFileName, {}, watchingSystemHost.system, watchingSystemHost.reportDiagnostic, watchingSystemHost.reportWatchDiagnostic);
}

function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem) {
function createWatchModeWithConfigFile(configFilePath: string, host: WatchedSystem, maxNumberOfFilesToIterateForInvalidation?: number) {
const watchingSystemHost = createWatchingSystemHost(host);
watchingSystemHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
const configFileResult = parseConfigFile(configFilePath, watchingSystemHost);
return ts.createWatchModeWithConfigFile(configFileResult, {}, watchingSystemHost);
}
Expand Down Expand Up @@ -1067,6 +1068,36 @@ namespace ts.tscWatch {
assert.equal(nowErrors[0].start, intialErrors[0].start - configFileContentComment.length);
assert.equal(nowErrors[1].start, intialErrors[1].start - configFileContentComment.length);
});

it("should not trigger recompilation because of program emit", () => {
const proj = "/user/username/projects/myproject";
const file1: FileOrFolder = {
path: `${proj}/file1.ts`,
content: "export const c = 30;"
};
const file2: FileOrFolder = {
path: `${proj}/src/file2.ts`,
content: `import {c} from "file1"; export const d = 30;`
};
const tsconfig: FileOrFolder = {
path: `${proj}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
module: "amd",
outDir: "build"
}
})
};
const host = createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: proj });
const watch = createWatchModeWithConfigFile(tsconfig.path, host, /*maxNumberOfFilesToIterateForInvalidation*/1);
checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]);

assert.isTrue(host.fileExists("build/file1.js"));
assert.isTrue(host.fileExists("build/src/file2.js"));

// This should be 0
host.checkTimeoutQueueLengthAndRun(0);
});
});

describe("tsc-watch emit with outFile or out setting", () => {
Expand Down
5 changes: 4 additions & 1 deletion src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,10 @@ namespace ts.server {
return !hasChanges;
}


/* @internal */
getCurrentProgram() {
return this.program;
}

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