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); } }); }