From 569ecabb0a6c83f8c0eb9249dd79cfa3e4e91231 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 22 Jun 2017 13:47:54 -0700 Subject: [PATCH] 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 --- src/compiler/core.ts | 9 ++++ src/compiler/program.ts | 19 +++----- src/compiler/sys.ts | 24 ++++++---- src/compiler/tsc.ts | 25 +++++------ src/compiler/types.ts | 3 +- src/harness/unittests/compileOnSave.ts | 6 +-- src/harness/unittests/programMissingFiles.ts | 40 +++++++---------- src/harness/unittests/projectErrors.ts | 4 +- .../unittests/tsserverProjectSystem.ts | 40 ++++++++--------- src/harness/unittests/typingsInstaller.ts | 2 +- src/server/project.ts | 45 ++++++++----------- src/server/server.ts | 10 ++--- 12 files changed, 112 insertions(+), 115 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 86ad483ccc3..93c7eed7aca 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -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(array: T[], makeKey: (value: T) => string): Map { + return arrayToMap(array, makeKey, () => true); + } + export function cloneMap(map: Map) { const clone = createMap(); copyEntries(map, clone); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index ac82fbba02c..7b62fde8570 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -472,7 +472,7 @@ namespace ts { resolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile) => loadWithLocalCache(typeReferenceDirectiveNames, containingFile, loader); } - const filesByName = createMap(); + const filesByName = createMap(); // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap(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 => 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 diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 8448c9b3615..ca6f85cf0dd 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -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) => { diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 8264282cba9..6e0aefd99a0 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -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(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 99d62091ed5..bb2994a487f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 7e262a1b257..79e1f921dcb 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -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(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path }); sendAffectedFileRequestAndCheckResult(session, request, [ diff --git a/src/harness/unittests/programMissingFiles.ts b/src/harness/unittests/programMissingFiles.ts index 26cc63590bd..668b684a250 100644 --- a/src/harness/unittests/programMissingFiles.ts +++ b/src/harness/unittests/programMissingFiles.ts @@ -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" + ]); }); }); } \ No newline at end of file diff --git a/src/harness/unittests/projectErrors.ts b/src/harness/unittests/projectErrors.ts index 30942fb2fdc..f250d4d9b36 100644 --- a/src/harness/unittests/projectErrors.ts +++ b/src/harness/unittests/projectErrors.ts @@ -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); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index d098c91eecf..66873ee2411 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -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 = 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 = 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 = 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({ type: "request", diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 6c43ea1c034..728eb057fc3 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -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); diff --git a/src/server/project.ts b/src/server/project.ts index 163510bad27..25573216b1c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -107,7 +107,7 @@ namespace ts.server { private rootFilesMap: Map = createMap(); private program: ts.Program; private externalFiles: SortedReadonlyArray; - private missingFilesMap: FileMap = createFileMap(); + private missingFilesMap: Map = createMap(); private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap(); private lastCachedUnresolvedImportsList: SortedReadonlyArray; @@ -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(); - 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; @@ -668,7 +661,7 @@ namespace ts.server { } isWatchedMissingFile(path: Path) { - return this.missingFilesMap.contains(path); + return this.missingFilesMap.has(path); } getScriptInfoLSHost(fileName: string) { diff --git a/src/server/server.ts b/src/server/server.ts index 9874969064e..dda87d16b6a 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -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); } }); }