Address PR feedback

Make Program.getMissingFilePaths required

Assume getMissingFilePaths always returns a defined value

Make getMissingFilePaths internal

Replace nullable-bool with enum

Update type to reflect possibility of undefined

Use deepEqual to simplify tests

Make condition const

Don't bother cleaning up map before freeing it

Switch from foreach to for-of to simplify debugging

Use a Map, rather than a FileMap, to track open FileWatchers

Fix compilation errors

Introduce and consume arrayToSet

Fix lint warnings about misplaced braces

Delete incorrect comment

Delete from map during iteration

Eliminate unnecessary type annotations
This commit is contained in:
Andrew Casey 2017-06-22 13:47:54 -07:00
parent 0f683ac2ad
commit 569ecabb0a
12 changed files with 112 additions and 115 deletions

View File

@ -1097,6 +1097,15 @@ namespace ts {
return result;
}
/**
* Creates a set from the elements of an array.
*
* @param array the array of input elements.
*/
export function arrayToSet<T>(array: T[], makeKey: (value: T) => string): Map<true> {
return arrayToMap<T, true>(array, makeKey, () => true);
}
export function cloneMap<T>(map: Map<T>) {
const clone = createMap<T>();
copyEntries(map, clone);

View File

@ -472,7 +472,7 @@ namespace ts {
resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(typeReferenceDirectiveNames, containingFile, loader);
}
const filesByName = createMap<SourceFile>();
const filesByName = createMap<SourceFile | undefined>();
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap<SourceFile>(fileName => fileName.toLowerCase()) : undefined;
@ -513,7 +513,7 @@ namespace ts {
}
}
const missingFilePaths = filesByName.getKeys().filter(p => !filesByName.get(p));
const missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
moduleResolutionCache = undefined;
@ -872,17 +872,12 @@ namespace ts {
// will be created until we encounter a change that prevents complete structure reuse.
// During this interval, creation of the file will go unnoticed. We expect this to be
// both rare and low-impact.
if (oldProgram.getMissingFilePaths) {
const missingFilePaths: Path[] = oldProgram.getMissingFilePaths() || emptyArray;
for (const missingFilePath of missingFilePaths) {
if (host.fileExists(missingFilePath)) {
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
}
if (oldProgram.getMissingFilePaths().some(missingFilePath => host.fileExists(missingFilePath))) {
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
}
for (const p of oldProgram.getMissingFilePaths()) {
filesByName.set(p, undefined);
}
for (const p of oldProgram.getMissingFilePaths()) {
filesByName.set(p, undefined);
}
// update fileName -> file mapping

View File

@ -4,7 +4,13 @@ declare function setTimeout(handler: (...args: any[]) => void, timeout: number):
declare function clearTimeout(handle: any): void;
namespace ts {
export type FileWatcherCallback = (fileName: string, removed?: boolean) => void;
export enum FileWatcherEventKind {
Created,
Changed,
Deleted
}
export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind) => void;
export type DirectoryWatcherCallback = (fileName: string) => void;
export interface WatchedFile {
fileName: string;
@ -174,7 +180,7 @@ namespace ts {
const callbacks = fileWatcherCallbacks.get(fileName);
if (callbacks) {
for (const fileCallback of callbacks) {
fileCallback(fileName);
fileCallback(fileName, FileWatcherEventKind.Changed);
}
}
}
@ -342,18 +348,20 @@ namespace ts {
function fileChanged(curr: any, prev: any) {
const isCurrZero = +curr.mtime === 0;
const isPrevZero = +prev.mtime === 0;
const added = !isCurrZero && isPrevZero;
const created = !isCurrZero && isPrevZero;
const deleted = isCurrZero && !isPrevZero;
// This value is consistent with poll() in createPollingWatchedFileSet()
// and depended upon by the file watchers created in Project.updateGraphWorker.
const removed = deleted ? true : (added ? false : undefined);
const eventKind = created
? FileWatcherEventKind.Created
: deleted
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
if (!added && !deleted && +curr.mtime <= +prev.mtime) {
if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) {
return;
}
callback(fileName, removed);
callback(fileName, eventKind);
}
},
watchDirectory: (directoryName, callback, recursive) => {

View File

@ -286,18 +286,15 @@ namespace ts {
setCachedProgram(compileResult.program);
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
if (compileResult.program.getMissingFilePaths) {
const missingPaths = compileResult.program.getMissingFilePaths() || [];
missingPaths.forEach((path: Path): void => {
const fileWatcher = sys.watchFile(path, (_fileName: string, removed?: boolean) => {
// removed = deleted ? true : (added ? false : undefined)
if (removed === false) {
fileWatcher.close();
startTimerForRecompilation();
}
});
const missingPaths = compileResult.program.getMissingFilePaths();
missingPaths.forEach(path => {
const fileWatcher = sys.watchFile(path, (_fileName, eventKind) => {
if (eventKind === FileWatcherEventKind.Created) {
fileWatcher.close();
startTimerForRecompilation();
}
});
}
});
}
function cachedFileExists(fileName: string): boolean {
@ -321,7 +318,7 @@ namespace ts {
const sourceFile = hostGetSourceFile(fileName, languageVersion, onError);
if (sourceFile && isWatchSet(compilerOptions) && sys.watchFile) {
// Attach a file watcher
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName: string, removed?: boolean) => sourceFileChanged(sourceFile, removed));
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName, eventKind) => sourceFileChanged(sourceFile, eventKind));
}
return sourceFile;
}
@ -343,10 +340,10 @@ namespace ts {
}
// If a source file changes, mark it as unwatched and start the recompilation timer
function sourceFileChanged(sourceFile: SourceFile, removed?: boolean) {
function sourceFileChanged(sourceFile: SourceFile, eventKind: FileWatcherEventKind) {
sourceFile.fileWatcher.close();
sourceFile.fileWatcher = undefined;
if (removed) {
if (eventKind === FileWatcherEventKind.Deleted) {
unorderedRemoveItem(rootFileNames, sourceFile.fileName);
}
startTimerForRecompilation();

View File

@ -2430,7 +2430,8 @@ namespace ts {
* Get a list of file names that were passed to 'createProgram' or referenced in a
* program source file but could not be located.
*/
getMissingFilePaths?(): Path[];
/* @internal */
getMissingFilePaths(): Path[];
/**
* Emits the JavaScript and declaration files. If targetSourceFile is not specified, then

View File

@ -208,7 +208,7 @@ namespace ts.projectSystem {
file1Consumer1.content = `let y = 10;`;
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer1.path, /*removed*/ false);
host.triggerFileWatcherCallback(file1Consumer1.path, FileWatcherEventKind.Changed);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
@ -225,7 +225,7 @@ namespace ts.projectSystem {
session.executeCommand(changeModuleFile1ShapeRequest1);
// Delete file1Consumer2
host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer2.path, /*removed*/ true);
host.triggerFileWatcherCallback(file1Consumer2.path, FileWatcherEventKind.Deleted);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
});
@ -475,7 +475,7 @@ namespace ts.projectSystem {
openFilesForSession([referenceFile1], session);
host.reloadFS([referenceFile1, configFile]);
host.triggerFileWatcherCallback(moduleFile1.path, /*removed*/ true);
host.triggerFileWatcherCallback(moduleFile1.path, FileWatcherEventKind.Deleted);
const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [

View File

@ -41,39 +41,33 @@ namespace ts {
const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.equal(missing.length, 0);
assert.deepEqual(missing, []);
});
it("handles missing root file", () => {
const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.equal(missing.length, 1);
assert.equal(missing[0].toString(), "d:/pretend/nonexistent.ts"); // Absolute path
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path
});
it("handles multiple missing root files", () => {
const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths().sort();
assert.equal(missing.length, 2);
assert.equal(missing[0].toString(), "d:/pretend/nonexistent0.ts");
assert.equal(missing[1].toString(), "d:/pretend/nonexistent1.ts");
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
});
it("handles a mix of present and missing root files", () => {
const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths().sort();
assert.equal(missing.length, 2);
assert.equal(missing[0].toString(), "d:/pretend/nonexistent0.ts");
assert.equal(missing[1].toString(), "d:/pretend/nonexistent1.ts");
assert.deepEqual(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
});
it("handles repeatedly specified root files", () => {
const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
const missing = program.getMissingFilePaths();
assert.isDefined(missing);
assert.equal(missing.length, 1);
assert.equal(missing[0].toString(), "d:/pretend/nonexistent.ts");
assert.deepEqual(missing, ["d:/pretend/nonexistent.ts"]);
});
it("normalizes file paths", () => {
@ -89,21 +83,21 @@ namespace ts {
const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
const missing = program.getMissingFilePaths().sort();
assert.isDefined(missing);
assert.equal(missing.length, 6);
assert.deepEqual(missing, [
// From absolute reference
"d:/imaginary/nonexistent1.ts",
// From absolute reference
assert.equal(missing[0].toString(), "d:/imaginary/nonexistent1.ts");
// From relative reference
"d:/pretend/nonexistent2.ts",
// From relative reference
assert.equal(missing[1].toString(), "d:/pretend/nonexistent2.ts");
// From unqualified reference
"d:/pretend/nonexistent3.ts",
// From unqualified reference
assert.equal(missing[2].toString(), "d:/pretend/nonexistent3.ts");
// From no-extension reference
assert.equal(missing[3].toString(), "d:/pretend/nonexistent4.d.ts");
assert.equal(missing[4].toString(), "d:/pretend/nonexistent4.ts");
assert.equal(missing[5].toString(), "d:/pretend/nonexistent4.tsx");
// From no-extension reference
"d:/pretend/nonexistent4.d.ts",
"d:/pretend/nonexistent4.ts",
"d:/pretend/nonexistent4.tsx"
]);
});
});
}

View File

@ -135,7 +135,7 @@ namespace ts.projectSystem {
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
host.triggerFileWatcherCallback(correctConfig.path, /*false*/);
host.triggerFileWatcherCallback(correctConfig.path, FileWatcherEventKind.Changed);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
@ -177,7 +177,7 @@ namespace ts.projectSystem {
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
host.triggerFileWatcherCallback(corruptedConfig.path, /*false*/);
host.triggerFileWatcherCallback(corruptedConfig.path, FileWatcherEventKind.Changed);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);

View File

@ -485,12 +485,12 @@ namespace ts.projectSystem {
}
}
triggerFileWatcherCallback(fileName: string, removed?: boolean): void {
triggerFileWatcherCallback(fileName: string, eventKind: FileWatcherEventKind): void {
const path = this.toPath(fileName);
const callbacks = this.watchedFiles.get(path);
if (callbacks) {
for (const callback of callbacks) {
callback(path, removed);
callback(path, eventKind);
}
}
}
@ -771,7 +771,7 @@ namespace ts.projectSystem {
// remove the tsconfig file
host.reloadFS(filesWithoutConfig);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfInferredProjects(projectService, 2);
checkNumberOfConfiguredProjects(projectService, 0);
@ -928,7 +928,7 @@ namespace ts.projectSystem {
"files": ["${commonFile1.path}"]
}`;
host.reloadFS(files);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfConfiguredProjects(projectService, 1);
checkProjectRootFiles(project, [commonFile1.path]);
@ -1002,7 +1002,7 @@ namespace ts.projectSystem {
"files": ["${file1.path}"]
}`;
host.reloadFS(files);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]);
checkNumberOfInferredProjects(projectService, 1);
});
@ -1433,7 +1433,7 @@ namespace ts.projectSystem {
};
host.reloadFS([file1, modifiedFile2, file3]);
host.triggerFileWatcherCallback(modifiedFile2.path, /*removed*/ false);
host.triggerFileWatcherCallback(modifiedFile2.path, FileWatcherEventKind.Changed);
checkNumberOfInferredProjects(projectService, 1);
checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]);
@ -1465,7 +1465,7 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { inferredProjects: 1 });
host.reloadFS([file1, file3]);
host.triggerFileWatcherCallback(file2.path, /*removed*/ true);
host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Deleted);
checkNumberOfProjects(projectService, { inferredProjects: 2 });
@ -1663,7 +1663,7 @@ namespace ts.projectSystem {
};
host.reloadFS([file1, file2, modifiedConfigFile]);
host.triggerFileWatcherCallback(configFile.path, /*removed*/ false);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
@ -1696,7 +1696,7 @@ namespace ts.projectSystem {
};
host.reloadFS([file1, file2, modifiedConfigFile]);
host.triggerFileWatcherCallback(configFile.path, /*removed*/ false);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
@ -1776,7 +1776,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(projectService.configuredProjects[0], [file1.path, file2.path, config.path]);
host.reloadFS([file1, file2]);
host.triggerFileWatcherCallback(config.path, /*removed*/ true);
host.triggerFileWatcherCallback(config.path, FileWatcherEventKind.Deleted);
checkNumberOfProjects(projectService, { inferredProjects: 2 });
checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]);
@ -2258,7 +2258,7 @@ namespace ts.projectSystem {
assert.isFalse(lastEvent.data.languageServiceEnabled, "Language service state");
host.reloadFS([f1, f2, configWithExclude]);
host.triggerFileWatcherCallback(config.path, /*removed*/ false);
host.triggerFileWatcherCallback(config.path, FileWatcherEventKind.Changed);
checkNumberOfProjects(projectService, { configuredProjects: 1 });
assert.isTrue(project.languageServiceEnabled, "Language service enabled");
@ -2492,7 +2492,7 @@ namespace ts.projectSystem {
// rename tsconfig.json back to lib.ts
host.reloadFS([f1, f2]);
host.triggerFileWatcherCallback(tsconfig.path, /*removed*/ true);
host.triggerFileWatcherCallback(tsconfig.path, FileWatcherEventKind.Deleted);
projectService.openExternalProject({
projectFileName: projectName,
rootFiles: toExternalFiles([f1.path, f2.path]),
@ -2637,7 +2637,7 @@ namespace ts.projectSystem {
checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, app.path, config1.path]);
host.reloadFS([libES5, libES2015Promise, app, config2]);
host.triggerFileWatcherCallback(config1.path);
host.triggerFileWatcherCallback(config1.path, FileWatcherEventKind.Changed);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectActualFiles(projectService.configuredProjects[0], [libES5.path, libES2015Promise.path, app.path, config2.path]);
@ -2860,7 +2860,7 @@ namespace ts.projectSystem {
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileOldPath);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@ -2868,7 +2868,7 @@ namespace ts.projectSystem {
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1]);
host.triggerFileWatcherCallback(moduleFileNewPath);
host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
@ -2912,7 +2912,7 @@ namespace ts.projectSystem {
const moduleFileNewPath = "/a/b/moduleFile1.ts";
moduleFile.path = moduleFileNewPath;
host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileOldPath);
host.triggerFileWatcherCallback(moduleFileOldPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@ -2920,7 +2920,7 @@ namespace ts.projectSystem {
moduleFile.path = moduleFileOldPath;
host.reloadFS([moduleFile, file1, configFile]);
host.triggerFileWatcherCallback(moduleFileNewPath);
host.triggerFileWatcherCallback(moduleFileNewPath, FileWatcherEventKind.Changed);
host.triggerDirectoryWatcherCallback("/a/b", moduleFile.path);
host.runQueuedTimeoutCallbacks();
diags = <server.protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
@ -3085,7 +3085,7 @@ namespace ts.projectSystem {
}
}`;
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
host.runQueuedTimeoutCallbacks();
serverEventManager.checkEventCountOfType("configFileDiag", 2);
@ -3093,7 +3093,7 @@ namespace ts.projectSystem {
"compilerOptions": {}
}`;
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
host.runQueuedTimeoutCallbacks();
serverEventManager.checkEventCountOfType("configFileDiag", 3);
});
@ -4021,7 +4021,7 @@ namespace ts.projectSystem {
configFile.content = configFileContentWithoutCommentLine;
host.reloadFS([file, configFile]);
host.triggerFileWatcherCallback(configFile.path);
host.triggerFileWatcherCallback(configFile.path, FileWatcherEventKind.Changed);
const diagsAfterEdit = session.executeCommand(<server.protocol.SemanticDiagnosticsSyncRequest>{
type: "request",

View File

@ -820,7 +820,7 @@ namespace ts.projectSystem {
installer.checkPendingCommands(/*expectedCount*/ 0);
host.reloadFS([f, fixedPackageJson]);
host.triggerFileWatcherCallback(fixedPackageJson.path, /*removed*/ false);
host.triggerFileWatcherCallback(fixedPackageJson.path, FileWatcherEventKind.Changed);
// expected install request
installer.installAll(/*expectedCount*/ 1);

View File

@ -107,7 +107,7 @@ namespace ts.server {
private rootFilesMap: Map<ScriptInfo> = createMap<ScriptInfo>();
private program: ts.Program;
private externalFiles: SortedReadonlyArray<string>;
private missingFilesMap: FileMap<FileWatcher> = createFileMap<FileWatcher>();
private missingFilesMap: Map<FileWatcher> = createMap<FileWatcher>();
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
@ -312,10 +312,7 @@ namespace ts.server {
this.lsHost = undefined;
// Clean up file watchers waiting for missing files
for (const p of this.missingFilesMap.getKeys()) {
this.missingFilesMap.get(p).close();
this.missingFilesMap.remove(p);
}
this.missingFilesMap.forEach(fileWatcher => fileWatcher.close());
this.missingFilesMap = undefined;
// signal language service to release source files acquired from document registry
@ -594,12 +591,12 @@ namespace ts.server {
const oldProgram = this.program;
this.program = this.languageService.getProgram();
let hasChanges = false;
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
if (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))) {
hasChanges = true;
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
if (hasChanges) {
if (oldProgram) {
for (const f of oldProgram.getSourceFiles()) {
if (this.program.getSourceFileByPath(f.path)) {
@ -612,39 +609,35 @@ namespace ts.server {
}
}
}
}
if (hasChanges && this.program.getMissingFilePaths) {
const missingFilePaths = this.program.getMissingFilePaths() || emptyArray;
const missingFilePathsSet = createMap<true>();
missingFilePaths.forEach(p => missingFilePathsSet.set(p, true));
const missingFilePaths = this.program.getMissingFilePaths();
const missingFilePathsSet = arrayToSet(missingFilePaths, p => p);
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
this.missingFilesMap.getKeys().forEach(p => {
if (!missingFilePathsSet.has(p)) {
this.missingFilesMap.get(p).close();
this.missingFilesMap.remove(p);
this.missingFilesMap.forEach((fileWatcher, missingFilePath) => {
if (!missingFilePathsSet.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
fileWatcher.close();
}
});
// Missing files that are not yet watched should be added to the map.
missingFilePaths.forEach(p => {
if (!this.missingFilesMap.contains(p)) {
const fileWatcher = this.projectService.host.watchFile(p, (_filename: string, removed?: boolean) => {
// removed = deleted ? true : (added ? false : undefined)
if (removed === false && this.missingFilesMap.contains(p)) {
for (const missingFilePath of missingFilePaths) {
if (!this.missingFilesMap.has(missingFilePath)) {
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (_filename: string, eventKind: FileWatcherEventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
fileWatcher.close();
this.missingFilesMap.remove(p);
this.missingFilesMap.delete(missingFilePath);
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.updateGraph();
}
});
this.missingFilesMap.set(p, fileWatcher);
this.missingFilesMap.set(missingFilePath, fileWatcher);
}
});
}
}
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
@ -668,7 +661,7 @@ namespace ts.server {
}
isWatchedMissingFile(path: Path) {
return this.missingFilesMap.contains(path);
return this.missingFilesMap.has(path);
}
getScriptInfoLSHost(fileName: string) {

View File

@ -522,16 +522,16 @@ namespace ts.server {
return;
}
// removed = deleted ? true : (added ? false : undefined)
// This value is consistent with sys.watchFile()
// and depended upon by the file watchers created in performCompilation() in tsc's executeCommandLine().
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
if (err) {
watchedFile.callback(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed);
}
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
watchedFile.mtime = stats.mtime;
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
const eventKind = watchedFile.mtime.getTime() === 0
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
watchedFile.callback(watchedFile.fileName, eventKind);
}
});
}