diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 4fb90ca7143..68fb36b9144 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -120,7 +120,7 @@ namespace ts { // Remove existing file info onDeleteValue: removeExistingFileInfo, // We will update in place instead of deleting existing value and adding new one - onExistingValue: (_key, existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) + onExistingValue: (existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile, hasInvalidatedResolution) } ); } @@ -137,7 +137,7 @@ namespace ts { return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined }; } - function removeExistingFileInfo(path: Path, existingFileInfo: FileInfo) { + function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) { registerChangedFile(path, existingFileInfo.fileName); emitHandler.removeScriptInfo(path); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 00e8065a9f9..bb4cf903a98 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2631,10 +2631,6 @@ namespace ts { return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs; } - export interface HostForCaching extends PartialSystem { - useCaseSensitiveFileNames: boolean; - } - export interface CachedHost { addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void; addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void; @@ -2649,11 +2645,15 @@ namespace ts { readonly directories: string[]; } - export function createCachedPartialSystem(host: HostForCaching): CachedPartialSystem { + export function createCachedPartialSystem(host: PartialSystem): CachedPartialSystem { const cachedReadDirectoryResult = createMap(); const getCurrentDirectory = memoize(() => host.getCurrentDirectory()); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); return { + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, + newLine: host.newLine, + readFile: (path, encoding) => host.readFile(path, encoding), + write: s => host.write(s), writeFile, fileExists, directoryExists, @@ -2663,7 +2663,8 @@ namespace ts { readDirectory, addOrDeleteFileOrFolder, addOrDeleteFile, - clearCache + clearCache, + exit: code => host.exit(code) }; function toPath(fileName: string) { diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c3b83e1ba7c..22459d36cb0 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -461,7 +461,6 @@ namespace ts { program: Program, missingFileWatches: Map, createMissingFileWatch: (missingFilePath: Path) => FileWatcher, - closeExistingMissingFilePathFileWatcher: (missingFilePath: Path, fileWatcher: FileWatcher) => void ) { const missingFilePaths = program.getMissingFilePaths(); const newMissingFilePathMap = arrayToSet(missingFilePaths); @@ -474,7 +473,7 @@ namespace ts { createNewValue: createMissingFileWatch, // Files that are no longer missing (e.g. because they are no longer required) // should no longer be watched. - onDeleteValue: closeExistingMissingFilePathFileWatcher + onDeleteValue: closeFileWatcher } ); } @@ -493,8 +492,7 @@ namespace ts { export function updateWatchingWildcardDirectories( existingWatchedForWildcards: Map, wildcardDirectories: Map, - watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher, - closeDirectoryWatcher: (directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flagsChanged: boolean) => void + watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher ) { mutateMap( existingWatchedForWildcards, @@ -503,8 +501,7 @@ namespace ts { // Create new watch and recursive info createNewValue: createWildcardDirectoryWatcher, // Close existing watch thats not needed any more - onDeleteValue: (directory, wildcardDirectoryWatcher) => - closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ false), + onDeleteValue: closeFileWatcherOf, // Close existing watch that doesnt match in the flags onExistingValue: updateWildcardDirectoryWatcher } @@ -518,13 +515,13 @@ namespace ts { }; } - function updateWildcardDirectoryWatcher(directory: string, wildcardDirectoryWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags) { + function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) { // Watcher needs to be updated if the recursive flags dont match - if (wildcardDirectoryWatcher.flags === flags) { + if (existingWatcher.flags === flags) { return; } - closeDirectoryWatcher(directory, wildcardDirectoryWatcher, /*flagsChanged*/ true); + existingWatcher.watcher.close(); existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags)); } } diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6d7c1f6c0e4..8001dd81bd9 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -27,14 +27,14 @@ namespace ts { } interface FailedLookupLocationsWatcher { - fileWatcher: FileWatcher; + watcher: FileWatcher; refCount: number; } export function createResolutionCache( toPath: (fileName: string) => Path, getCompilerOptions: () => CompilerOptions, - watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => FileWatcher, + watchForFailedLookupLocation: (failedLookupLocation: string, failedLookupLocationPath: Path) => FileWatcher, log: (s: string) => void, projectName?: string, getGlobalCache?: () => string | undefined): ResolutionCache { @@ -69,10 +69,7 @@ namespace ts { function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache - clearMap(failedLookupLocationsWatches, (failedLookupLocationPath, failedLookupLocationWatcher) => { - log(`Watcher: FailedLookupLocations: Status: ForceClose: LocationPath: ${failedLookupLocationPath}, refCount: ${failedLookupLocationWatcher.refCount}`); - failedLookupLocationWatcher.fileWatcher.close(); - }); + clearMap(failedLookupLocationsWatches, closeFileWatcherOf); resolvedModuleNames.clear(); resolvedTypeReferenceDirectives.clear(); } @@ -142,7 +139,7 @@ namespace ts { } else { resolution = loader(name, containingFile, compilerOptions, host); - updateFailedLookupLocationWatches(containingFile, name, existingResolution && existingResolution.failedLookupLocations, resolution.failedLookupLocations); + updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) { @@ -205,46 +202,44 @@ namespace ts { m => m.resolvedModule, r => r.resolvedFileName, logChanges); } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); if (failedLookupLocationWatcher) { failedLookupLocationWatcher.refCount++; - log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name} refCount: ${failedLookupLocationWatcher.refCount}`); + log(`Watcher: FailedLookupLocations: Status: Using existing watcher: Location: ${failedLookupLocation}`); } else { - log(`Watcher: FailedLookupLocations: Status: new watch: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - const fileWatcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name); - failedLookupLocationsWatches.set(failedLookupLocationPath, { fileWatcher, refCount: 1 }); + const watcher = watchForFailedLookupLocation(failedLookupLocation, failedLookupLocationPath); + failedLookupLocationsWatches.set(failedLookupLocationPath, { watcher, refCount: 1 }); } } - function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { const failedLookupLocationWatcher = failedLookupLocationsWatches.get(failedLookupLocationPath); Debug.assert(!!failedLookupLocationWatcher); failedLookupLocationWatcher.refCount--; if (failedLookupLocationWatcher.refCount) { - log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}: refCount: ${failedLookupLocationWatcher.refCount}`); + log(`Watcher: FailedLookupLocations: Status: Removing existing watcher: Location: ${failedLookupLocation}`); } else { - log(`Watcher: FailedLookupLocations: Status: Closing the file watcher: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - failedLookupLocationWatcher.fileWatcher.close(); + failedLookupLocationWatcher.watcher.close(); failedLookupLocationsWatches.delete(failedLookupLocationPath); } } - type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) => void; - function withFailedLookupLocations(failedLookupLocations: ReadonlyArray, containingFile: string, name: string, fn: FailedLookupLocationAction) { + type FailedLookupLocationAction = (failedLookupLocation: string, failedLookupLocationPath: Path) => void; + function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction) { forEach(failedLookupLocations, failedLookupLocation => { - fn(failedLookupLocation, toPath(failedLookupLocation), containingFile, name); + fn(failedLookupLocation, toPath(failedLookupLocation)); }); } - function updateFailedLookupLocationWatches(containingFile: string, name: string, existingFailedLookupLocations: ReadonlyArray | undefined, failedLookupLocations: ReadonlyArray) { + function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { // Watch all the failed lookup locations - withFailedLookupLocations(failedLookupLocations, containingFile, name, watchFailedLookupLocation); + withFailedLookupLocations(failedLookupLocations, watchFailedLookupLocation); // Close existing watches for the failed locations - withFailedLookupLocations(existingFailedLookupLocations, containingFile, name, closeFailedLookupLocationWatcher); + withFailedLookupLocations(existingFailedLookupLocations, closeFailedLookupLocationWatcher); } function invalidateResolutionCacheOfDeletedFile( @@ -255,8 +250,8 @@ namespace ts { cache.forEach((value, path) => { if (path === deletedFilePath) { cache.delete(path); - value.forEach((resolution, name) => { - withFailedLookupLocations(resolution.failedLookupLocations, path, name, closeFailedLookupLocationWatcher); + value.forEach(resolution => { + withFailedLookupLocations(resolution.failedLookupLocations, closeFailedLookupLocationWatcher); }); } else if (value) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 982d6793819..7a419be7c2d 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -34,6 +34,10 @@ namespace ts { * Partial interface of the System thats needed to support the caching of directory structure */ export interface PartialSystem { + newLine: string; + useCaseSensitiveFileNames: boolean; + write(s: string): void; + readFile(path: string, encoding?: string): string | undefined; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; fileExists(path: string): boolean; directoryExists(path: string): boolean; @@ -41,6 +45,7 @@ namespace ts { getCurrentDirectory(): string; getDirectories(path: string): string[]; readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[]; + exit(exitCode?: number): void; } export interface System extends PartialSystem { @@ -48,7 +53,6 @@ namespace ts { newLine: string; useCaseSensitiveFileNames: boolean; write(s: string): void; - readFile(path: string, encoding?: string): string | undefined; getFileSize?(path: string): number; /** * @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that @@ -65,7 +69,6 @@ namespace ts { */ createHash?(data: string): string; getMemoryUsage?(): number; - exit(exitCode?: number): void; realpath?(path: string): string; /*@internal*/ getEnvironmentVariable(name: string): string; /*@internal*/ tryEnableSourceMapsForHost?(): void; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f09ce446491..7e43d793f0b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3490,17 +3490,15 @@ namespace ts { /** * clears already present map by calling onDeleteExistingValue callback before deleting that key/value */ - export function clearMap(map: Map, onDeleteValue: (key: string, existingValue: T) => void) { + export function clearMap(map: Map, onDeleteValue: (existingValue: T, key: string) => void) { // Remove all - map.forEach((existingValue, key) => { - onDeleteValue(key, existingValue); - }); + map.forEach(onDeleteValue); map.clear(); } export interface MutateMapOptions { createNewValue(key: string, valueInNewMap: U): T; - onDeleteValue(key: string, existingValue: T): void; + onDeleteValue(existingValue: T, key: string): void; /** * If present this is called with the key when there is value for that key both in new map as well as existing map provided @@ -3508,7 +3506,7 @@ namespace ts { * If the key is removed, caller will get callback of createNewValue for that key. * If this callback is not provided, the value of such keys is not updated. */ - onExistingValue?(key: string, existingValue: T, valueInNewMap: U): void; + onExistingValue?(existingValue: T, valueInNewMap: U, key: string): void; } /** @@ -3524,11 +3522,11 @@ namespace ts { // Not present any more in new map, remove it if (valueInNewMap === undefined) { map.delete(key); - onDeleteValue(key, existingValue); + onDeleteValue(existingValue, key); } // If present notify about existing values else if (onExistingValue) { - onExistingValue(key, existingValue, valueInNewMap); + onExistingValue(existingValue, valueInNewMap, key); } }); @@ -3544,6 +3542,63 @@ namespace ts { clearMap(map, options.onDeleteValue); } } + + export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher { + return host.watchFile(file, cb); + } + + export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher { + const watcherCaption = `FileWatcher:: `; + return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb); + } + + export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void; + export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher { + return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path)); + } + + export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher { + const watcherCaption = `FileWatcher:: `; + return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path); + } + + export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher { + const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; + return host.watchDirectory(directory, fileName => { + cb(getNormalizedAbsolutePath(fileName, directory)); + }, recursive); + } + + export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { + const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `; + return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags); + } + + type WatchCallback = (fileName: string, cbOptional1?: T, optional?: U) => void; + type AddWatch = (host: System, file: string, cb: WatchCallback, optional?: U) => FileWatcher; + function createWatcherWithLogging(addWatch: AddWatch, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback, optional?: U): FileWatcher { + const info = `PathInfo: ${file}`; + log(`${watcherCaption}Added: ${info}`); + const watcher = addWatch(host, file, (fileName, cbOptional1?) => { + const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : ""; + log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`); + cb(fileName, cbOptional1, optional); + }, optional); + return { + close: () => { + log(`${watcherCaption}Close: ${info}`); + watcher.close(); + } + }; + } + + export function closeFileWatcher(watcher: FileWatcher) { + watcher.close(); + } + + export function closeFileWatcherOf(objWithWatcher: T) { + objWithWatcher.watcher.close(); + } } namespace ts { diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 084c707cea8..2a23e66763b 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -4,8 +4,7 @@ namespace ts { export type DiagnosticReporter = (diagnostic: Diagnostic) => void; - export type DiagnosticWorker = (diagnostic: Diagnostic, host: FormatDiagnosticsHost, system: System) => void; - export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; + export type ParseConfigFile = (configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter) => ParsedCommandLine; export interface WatchingSystemHost { // FS system to use system: System; @@ -19,7 +18,7 @@ namespace ts { // Callbacks to do custom action before creating program and after creating program beforeCompile(compilerOptions: CompilerOptions): void; - afterCompile(host: System, program: Program, builder: Builder): void; + afterCompile(host: PartialSystem, program: Program, builder: Builder): void; } const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = sys ? { @@ -62,7 +61,7 @@ namespace ts { system.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + host.getNewLine()); } - export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { + export function parseConfigFile(configFileName: string, optionsToExtend: CompilerOptions, system: PartialSystem, reportDiagnostic: DiagnosticReporter, reportWatchDiagnostic: DiagnosticReporter): ParsedCommandLine { let configFileText: string; try { configFileText = system.readFile(configFileName); @@ -90,7 +89,7 @@ namespace ts { return configParseResult; } - function reportEmittedFiles(files: string[], system: System): void { + function reportEmittedFiles(files: string[], system: PartialSystem): void { if (!files || files.length === 0) { return; } @@ -101,7 +100,7 @@ namespace ts { } } - export function handleEmitOutputAndReportErrors(system: System, program: Program, + export function handleEmitOutputAndReportErrors(system: PartialSystem, program: Program, emittedFiles: string[], emitSkipped: boolean, diagnostics: Diagnostic[], reportDiagnostic: DiagnosticReporter ): ExitStatus { @@ -142,7 +141,7 @@ namespace ts { afterCompile: compileWatchedProgram, }; - function compileWatchedProgram(host: System, program: Program, builder: Builder) { + function compileWatchedProgram(host: PartialSystem, program: Program, builder: Builder) { // First get and report any syntactic errors. let diagnostics = program.getSyntacticDiagnostics(); let reportSemanticDiagnostics = false; @@ -249,26 +248,28 @@ namespace ts { let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations + const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; + const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop; + const watchFile = loggingEnabled ? ts.addFileWatcherWithLogging : ts.addFileWatcher; + const watchFilePath = loggingEnabled ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher; + const watchDirectory = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher; + watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - let host: System; + const host = configFileName ? createCachedPartialSystem(system) : system; if (configFileName) { - host = createCachedSystem(system); - configFileWatcher = system.watchFile(configFileName, onConfigFileChanged); - } - else { - host = system; + configFileWatcher = watchFile(system, configFileName, scheduleProgramReload, writeLog); } const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); // Cache for the module resolution const resolutionCache = createResolutionCache( fileName => toPath(fileName), () => compilerOptions, watchFailedLookupLocation, - s => writeLog(s) + writeLog ); // There is no extra check needed since we can just rely on the program to decide emit @@ -303,7 +304,7 @@ namespace ts { builder.onProgramUpdateGraph(program, hasInvalidatedResolution); // Update watches - updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath, closeMissingFilePathWatcher); + updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath); if (missingFilePathsRequestedForRelease) { // These are the paths that program creater told us as not in use any more but were missing on the disk. // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, @@ -324,7 +325,7 @@ namespace ts { function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { const newLine = getNewLineCharacter(options, system); - const realpath = host.realpath && ((path: string) => host.realpath(path)); + const realpath = system.realpath && ((path: string) => system.realpath(path)); return { getSourceFile: getVersionedSourceFile, @@ -333,14 +334,14 @@ namespace ts { getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, getCurrentDirectory: memoize(() => host.getCurrentDirectory()), - useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames, + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, getCanonicalFileName, getNewLine: () => newLine, fileExists, - readFile: fileName => host.readFile(fileName), - trace: (s: string) => host.write(s + newLine), + readFile: fileName => system.readFile(fileName), + trace: (s: string) => system.write(s + newLine), directoryExists: directoryName => host.directoryExists(directoryName), - getEnvironmentVariable: name => host.getEnvironmentVariable ? host.getEnvironmentVariable(name) : "", + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", getDirectories: (path: string) => host.getDirectories(path), realpath, resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), @@ -365,7 +366,7 @@ namespace ts { } function getDefaultLibLocation(): string { - return getDirectoryPath(normalizePath(host.getExecutingFilePath())); + return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile { @@ -390,7 +391,7 @@ namespace ts { hostSourceFile.sourceFile = sourceFile; sourceFile.version = hostSourceFile.version.toString(); if (!hostSourceFile.fileWatcher) { - hostSourceFile.fileWatcher = watchSourceFileForChanges(path); + hostSourceFile.fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); } } else { @@ -403,7 +404,7 @@ namespace ts { let fileWatcher: FileWatcher; if (sourceFile) { sourceFile.version = "0"; - fileWatcher = watchSourceFileForChanges(path); + fileWatcher = watchFilePath(system, fileName, onSourceFileChange, path, writeLog); sourceFilesCache.set(path, { sourceFile, version: 0, fileWatcher }); } else { @@ -418,7 +419,7 @@ namespace ts { let text: string; try { performance.mark("beforeIORead"); - text = host.readFile(fileName, compilerOptions.charset); + text = system.readFile(fileName, compilerOptions.charset); performance.mark("afterIORead"); performance.measure("I/O Read", "beforeIORead", "afterIORead"); } @@ -502,7 +503,7 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); needsReload = false; - const cachedHost = host as CachedSystem; + const cachedHost = host as CachedPartialSystem; cachedHost.clearCache(); const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); rootFileNames = configParseResult.fileNames; @@ -517,13 +518,7 @@ namespace ts { watchConfigFileWildCardDirectories(); } - function watchSourceFileForChanges(path: Path) { - return host.watchFile(path, (fileName, eventKind) => onSourceFileChange(fileName, path, eventKind)); - } - - function onSourceFileChange(fileName: string, path: Path, eventKind: FileWatcherEventKind) { - writeLog(`Source file path : ${path} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - + function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { updateCachedSystemWithFile(fileName, path, eventKind); const hostSourceFile = sourceFilesCache.get(path); if (hostSourceFile) { @@ -553,35 +548,29 @@ namespace ts { function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { if (configFileName) { - (host as CachedSystem).addOrDeleteFile(fileName, path, eventKind); + (host as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); } } - function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { - return host.watchFile(failedLookupLocation, (fileName, eventKind) => onFailedLookupLocationChange(fileName, eventKind, failedLookupLocation, failedLookupLocationPath, containingFile, name)); + function watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { + return watchFilePath(system, failedLookupLocation, onFailedLookupLocationChange, failedLookupLocationPath, writeLog); } - function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { - writeLog(`Failed lookup location : ${failedLookupLocation} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName} containingFile: ${containingFile}, name: ${name}`); + function onFailedLookupLocationChange(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { updateCachedSystemWithFile(fileName, failedLookupLocationPath, eventKind); resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); scheduleProgramUpdate(); } function watchMissingFilePath(missingFilePath: Path) { - return host.watchFile(missingFilePath, (fileName, eventKind) => onMissingFileChange(fileName, missingFilePath, eventKind)); + return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog); } - function closeMissingFilePathWatcher(_missingFilePath: Path, fileWatcher: FileWatcher) { - fileWatcher.close(); - } - - function onMissingFileChange(fileName: string, missingFilePath: Path, eventKind: FileWatcherEventKind) { - writeLog(`Missing file path : ${missingFilePath} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); + function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { updateCachedSystemWithFile(fileName, missingFilePath, eventKind); if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { - closeMissingFilePathWatcher(missingFilePath, missingFilesMap.get(missingFilePath)); + missingFilesMap.get(missingFilePath).close(); missingFilesMap.delete(missingFilePath); // Delete the entry in the source files cache so that new source file is created @@ -596,19 +585,12 @@ namespace ts { updateWatchingWildcardDirectories( watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), createMapFromTemplate(configFileWildCardDirectories), - watchWildCardDirectory, - stopWatchingWildCardDirectory + watchWildCardDirectory ); } function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) { - return host.watchDirectory(directory, fileOrFolder => - onFileAddOrRemoveInWatchedDirectory(getNormalizedAbsolutePath(fileOrFolder, directory)), - (flags & WatchDirectoryFlags.Recursive) !== 0); - } - - function stopWatchingWildCardDirectory(_directory: string, { watcher }: WildcardDirectoryWatcher, _recursiveChanged: boolean) { - watcher.close(); + return watchDirectory(system, directory, onFileAddOrRemoveInWatchedDirectory, flags, writeLog); } function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) { @@ -617,7 +599,7 @@ namespace ts { const fileOrFolderPath = toPath(fileOrFolder); // Since the file existance changed, update the sourceFiles cache - (host as CachedSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); removeSourceFile(fileOrFolderPath); // If a change was made inside "folder/file", node will trigger the callback twice: @@ -628,8 +610,6 @@ namespace ts { return; } - writeLog(`Project: ${configFileName} Detected file add/remove of supported extension: ${fileOrFolder}`); - // Reload is pending, do the reload if (!needsReload) { const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); @@ -643,70 +623,8 @@ namespace ts { } } - function onConfigFileChanged(fileName: string, eventKind: FileWatcherEventKind) { - writeLog(`Config file : ${configFileName} changed: ${FileWatcherEventKind[eventKind]}, fileName: ${fileName}`); - scheduleProgramReload(); - } - - function writeLog(s: string) { - const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics; - if (hasDiagnostics) { - host.write(s); - } - } - function computeHash(data: string) { return system.createHash ? system.createHash(data) : data; } } - - interface CachedSystem extends System, CachedHost { - } - - function createCachedSystem(host: System): CachedSystem { - const getFileSize = host.getFileSize ? (path: string) => host.getFileSize(path) : undefined; - const watchFile = host.watchFile ? (path: string, callback: FileWatcherCallback, pollingInterval?: number) => host.watchFile(path, callback, pollingInterval) : undefined; - const watchDirectory = host.watchDirectory ? (path: string, callback: DirectoryWatcherCallback, recursive?: boolean) => host.watchDirectory(path, callback, recursive) : undefined; - const getModifiedTime = host.getModifiedTime ? (path: string) => host.getModifiedTime(path) : undefined; - const createHash = host.createHash ? (data: string) => host.createHash(data) : undefined; - const getMemoryUsage = host.getMemoryUsage ? () => host.getMemoryUsage() : undefined; - const realpath = host.realpath ? (path: string) => host.realpath(path) : undefined; - const tryEnableSourceMapsForHost = host.tryEnableSourceMapsForHost ? () => host.tryEnableSourceMapsForHost() : undefined; - const setTimeout = host.setTimeout ? (callback: (...args: any[]) => void, ms: number, ...args: any[]) => host.setTimeout(callback, ms, ...args) : undefined; - const clearTimeout = host.clearTimeout ? (timeoutId: any) => host.clearTimeout(timeoutId) : undefined; - - const cachedPartialSystem = createCachedPartialSystem(host); - return { - args: host.args, - newLine: host.newLine, - useCaseSensitiveFileNames: host.useCaseSensitiveFileNames, - write: s => host.write(s), - readFile: (path, encoding?) => host.readFile(path, encoding), - getFileSize, - writeFile: (fileName, data, writeByteOrderMark?) => cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark), - watchFile, - watchDirectory, - resolvePath: path => host.resolvePath(path), - fileExists: fileName => cachedPartialSystem.fileExists(fileName), - directoryExists: dir => cachedPartialSystem.directoryExists(dir), - createDirectory: dir => cachedPartialSystem.createDirectory(dir), - getExecutingFilePath: () => host.getExecutingFilePath(), - getCurrentDirectory: () => cachedPartialSystem.getCurrentDirectory(), - getDirectories: dir => cachedPartialSystem.getDirectories(dir), - readDirectory: (path, extensions, excludes, includes, depth) => cachedPartialSystem.readDirectory(path, extensions, excludes, includes, depth), - getModifiedTime, - createHash, - getMemoryUsage, - exit: exitCode => host.exit(exitCode), - realpath, - getEnvironmentVariable: name => host.getEnvironmentVariable(name), - tryEnableSourceMapsForHost, - debugMode: host.debugMode, - setTimeout, - clearTimeout, - addOrDeleteFileOrFolder: (fileOrFolder, fileOrFolderPath) => cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath), - addOrDeleteFile: (file, filePath, eventKind) => cachedPartialSystem.addOrDeleteFile(file, filePath, eventKind), - clearCache: () => cachedPartialSystem.clearCache() - }; - } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 385ade0b816..292f6e3b90a 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -248,22 +248,7 @@ namespace ts.server { TypeRoot = "Type root of the project", ClosedScriptInfo = "Closed Script info", ConfigFileForInferredRoot = "Config file for the inferred project root", - FailedLookupLocation = "Failed lookup locations in module resolution" - } - - /* @internal */ - export const enum WatcherCloseReason { - ProjectClose = "Project close", - NotNeeded = "After project update isnt required any more", - FileCreated = "File got created", - RecursiveChanged = "Recursive changed for the watch", - ProjectReloadHitMaxSize = "Project reloaded and hit the max file size capacity", - OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more", - OrphanScriptInfo = "Removing Orphan script info as part of cleanup", - FileDeleted = "File was deleted", - FileOpened = "File opened", - ConfigProjectCreated = "Config file project created", - FileClosed = "File is closed" + FailedLookupLocation = "Directory of Failed lookup locations in module resolution" } const enum ConfigFileWatcherStatus { @@ -276,9 +261,6 @@ namespace ts.server { RootOfInferredProjectFalse = "Open file was set as not inferred root", } - /* @internal */ - export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; - interface ConfigFileExistenceInfo { /** * Cached value of existence of config file @@ -318,6 +300,10 @@ namespace ts.server { allowLocalPluginLoads?: boolean; } + type WatchFile = (host: ServerHost, file: string, cb: FileWatcherCallback, watchType: WatchType, project?: Project) => FileWatcher; + type WatchFilePath = (host: ServerHost, file: string, cb: FilePathWatcherCallback, path: Path, watchType: WatchType, project?: Project) => FileWatcher; + type WatchDirectory = (host: ServerHost, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, watchType: WatchType, project?: Project) => FileWatcher; + export class ProjectService { public readonly typingsCache: TypingsCache; @@ -392,6 +378,13 @@ namespace ts.server { /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects = createMap(); + /*@internal*/ + readonly watchFile: WatchFile; + /*@internal*/ + readonly watchFilePath: WatchFilePath; + /*@internal*/ + readonly watchDirectory: WatchDirectory; + constructor(opts: ProjectServiceOptions) { this.host = opts.host; this.logger = opts.logger; @@ -422,6 +415,21 @@ namespace ts.server { }; this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.currentDirectory); + if (this.logger.hasLevel(LogLevel.verbose)) { + this.watchFile = (host, file, cb, watchType, project) => ts.addFileWatcherWithLogging(host, file, cb, this.createWatcherLog(watchType, project)); + this.watchFilePath = (host, file, cb, path, watchType, project) => ts.addFilePathWatcherWithLogging(host, file, cb, path, this.createWatcherLog(watchType, project)); + this.watchDirectory = (host, dir, cb, flags, watchType, project) => ts.addDirectoryWatcherWithLogging(host, dir, cb, flags, this.createWatcherLog(watchType, project)); + } + else { + this.watchFile = ts.addFileWatcher; + this.watchFilePath = ts.addFilePathWatcher; + this.watchDirectory = ts.addDirectoryWatcher; + } + } + + private createWatcherLog(watchType: WatchType, project: Project | undefined): (s: string) => void { + const detailedInfo = ` Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`; + return s => this.logger.info(s + detailedInfo); } toPath(fileName: string, basePath = this.currentDirectory) { @@ -674,7 +682,7 @@ namespace ts.server { else if (!info.isScriptOpen()) { if (info.containingProjects.length === 0) { // Orphan script info, remove it as we can always reload it on next open file request - this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange); + this.stopWatchingScriptInfo(info); this.filenameToScriptInfo.delete(info.path); } else { @@ -687,7 +695,7 @@ namespace ts.server { } private handleDeletedFile(info: ScriptInfo) { - this.stopWatchingScriptInfo(info, WatcherCloseReason.FileDeleted); + this.stopWatchingScriptInfo(info); // TODO: handle isOpen = true case @@ -705,8 +713,8 @@ namespace ts.server { } /* @internal */ - onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: NormalizedPath) { - project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); + onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: string) { + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); project.updateTypes(); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } @@ -717,8 +725,8 @@ namespace ts.server { * @param fileName the absolute file name that changed in watched directory */ /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: NormalizedPath) { - project.getCachedServerHost().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); + onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: string) { + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); const configFilename = project.getConfigFilePath(); // If a change was made inside "folder/file", node will trigger the callback twice: @@ -730,7 +738,7 @@ namespace ts.server { } const configFileSpecs = project.configFileSpecs; - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions); + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); @@ -923,7 +931,7 @@ namespace ts.server { this.filenameToScriptInfo.forEach(info => { if (!info.isScriptOpen() && info.isOrphan()) { // if there are not projects that include this script info - delete it - this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo); + this.stopWatchingScriptInfo(info); this.filenameToScriptInfo.delete(info.path); } }); @@ -967,10 +975,7 @@ namespace ts.server { // close existing watcher if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) { const configFileName = project.getConfigFilePath(); - this.closeFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - configFileExistenceInfo.configFileWatcherForRootOfInferredProject, WatcherCloseReason.ConfigProjectCreated - ); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject.close(); configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined; this.logConfigFileWatchUpdate(configFileName, project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); } @@ -1002,11 +1007,7 @@ namespace ts.server { // created when any of the script infos are added as root of inferred project if (this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) { Debug.assert(!configFileExistenceInfo.configFileWatcherForRootOfInferredProject); - configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ); - this.logConfigFileWatchUpdate(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.createConfigFileWatcherOfConfigFileExistence(configFileName, closedProject.canonicalConfigFilePath, configFileExistenceInfo); } } else { @@ -1016,7 +1017,7 @@ namespace ts.server { } private logConfigFileWatchUpdate(configFileName: NormalizedPath, canonicalConfigFilePath: string, configFileExistenceInfo: ConfigFileExistenceInfo, status: ConfigFileWatcherStatus) { - if (!this.logger.loggingEnabled()) { + if (!this.logger.hasLevel(LogLevel.verbose)) { return; } const inferredRoots: string[] = []; @@ -1036,21 +1037,32 @@ namespace ts.server { this.logger.info(`ConfigFilePresence:: Current Watches: ${watches}:: File: ${configFileName} Currently impacted open files: RootsOfInferredProjects: ${inferredRoots} OtherOpenFiles: ${otherFiles} Status: ${status}`); } + /** + * Create the watcher for the configFileExistenceInfo + */ + private createConfigFileWatcherOfConfigFileExistence( + configFileName: NormalizedPath, + canonicalConfigFilePath: string, + configFileExistenceInfo: ConfigFileExistenceInfo + ) { + configFileExistenceInfo.configFileWatcherForRootOfInferredProject = this.watchFile( + this.host, + configFileName, + (_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind), + WatchType.ConfigFileForInferredRoot + ); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + } + /** * Close the config file watcher in the cached ConfigFileExistenceInfo * if there arent any open files that are root of inferred project */ - private closeConfigFileWatcherOfConfigFileExistenceInfo( - configFileName: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo, - reason: WatcherCloseReason - ) { + private closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo: ConfigFileExistenceInfo) { // Close the config file watcher if there are no more open files that are root of inferred project if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject && !this.configFileExistenceImpactsRootOfInferredProject(configFileExistenceInfo)) { - this.closeFileWatcher( - WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - configFileExistenceInfo.configFileWatcherForRootOfInferredProject, reason - ); + configFileExistenceInfo.configFileWatcherForRootOfInferredProject.close(); configFileExistenceInfo.configFileWatcherForRootOfInferredProject = undefined; } } @@ -1074,9 +1086,7 @@ namespace ts.server { if (infoIsRootOfInferredProject) { // But if it is a root, it could be the last script info that is root of inferred project // and hence we would need to close the config file watcher - this.closeConfigFileWatcherOfConfigFileExistenceInfo( - configFileName, configFileExistenceInfo, WatcherCloseReason.FileClosed - ); + this.closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo); } // If there are no open files that are impacted by configFileExistenceInfo after closing this script info @@ -1097,27 +1107,24 @@ namespace ts.server { startWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { Debug.assert(info.isScriptOpen()); this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { - let configFilePresenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); - if (!configFilePresenceInfo) { + let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); + if (!configFileExistenceInfo) { // Create the cache - configFilePresenceInfo = { + configFileExistenceInfo = { exists: this.host.fileExists(configFileName), openFilesImpactedByConfigFile: createMap() }; - this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFilePresenceInfo); + this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo); } // Set this file as the root of inferred project - configFilePresenceInfo.openFilesImpactedByConfigFile.set(info.path, true); - this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue); + configFileExistenceInfo.openFilesImpactedByConfigFile.set(info.path, true); + this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectTrue); // If there is no configured project for this config file, add the file watcher - if (!configFilePresenceInfo.configFileWatcherForRootOfInferredProject && + if (!configFileExistenceInfo.configFileWatcherForRootOfInferredProject && !this.getConfiguredProjectByCanonicalConfigFilePath(canonicalConfigFilePath)) { - configFilePresenceInfo.configFileWatcherForRootOfInferredProject = this.addFileWatcher(WatchType.ConfigFileForInferredRoot, /*project*/ undefined, configFileName, - (_fileName, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind) - ); - this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFilePresenceInfo, ConfigFileWatcherStatus.UpdatedCallback); + this.createConfigFileWatcherOfConfigFileExistence(configFileName, canonicalConfigFilePath, configFileExistenceInfo); } }); } @@ -1126,7 +1133,7 @@ namespace ts.server { * This is called by inferred project whenever root script info is removed from it */ /* @internal */ - stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo, reason: WatcherCloseReason) { + stopWatchingConfigFilesForInferredProjectRoot(info: ScriptInfo) { this.forEachConfigFileLocation(info, (configFileName, canonicalConfigFilePath) => { const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath); if (configFileExistenceInfo && configFileExistenceInfo.openFilesImpactedByConfigFile.has(info.path)) { @@ -1137,9 +1144,7 @@ namespace ts.server { this.logConfigFileWatchUpdate(configFileName, canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.RootOfInferredProjectFalse); // Close the config file watcher - this.closeConfigFileWatcherOfConfigFileExistenceInfo( - configFileName, configFileExistenceInfo, reason - ); + this.closeConfigFileWatcherOfConfigFileExistenceInfo(configFileExistenceInfo); } }); } @@ -1248,7 +1253,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: CachedServerHost) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: PartialSystem) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -1385,8 +1390,8 @@ namespace ts.server { } private createConfiguredProject(configFileName: NormalizedPath, clientFileName?: string) { - const cachedServerHost = new CachedServerHost(this.host); - const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedServerHost); + const cachedPartialSystem = createCachedPartialSystem(this.host); + const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedPartialSystem); this.logger.info(`Opened configuration file ${configFileName}`); const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); const project = new ConfiguredProject( @@ -1397,12 +1402,16 @@ namespace ts.server { projectOptions.compilerOptions, languageServiceEnabled, projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave, - cachedServerHost); + cachedPartialSystem); project.configFileSpecs = configFileSpecs; // TODO: We probably should also watch the configFiles that are extended - project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project, - configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind) + project.configFileWatcher = this.watchFile( + this.host, + configFileName, + (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind), + WatchType.ConfigFilePath, + project ); if (languageServiceEnabled) { project.watchWildcards(projectOptions.wildcardDirectories); @@ -1490,7 +1499,7 @@ namespace ts.server { /* @internal */ reloadConfiguredProject(project: ConfiguredProject) { // At this point, there is no reason to not have configFile in the host - const host = project.getCachedServerHost(); + const host = project.getCachedPartialSystem(); // Clear the cache since we are reloading the project from disk host.clearCache(); @@ -1505,8 +1514,8 @@ namespace ts.server { project.setProjectErrors(configFileErrors); if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { project.disableLanguageService(); - project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize); - project.stopWatchingTypeRoots(WatcherCloseReason.ProjectReloadHitMaxSize); + project.stopWatchingWildCards(); + project.stopWatchingTypeRoots(); } else { project.enableLanguageService(); @@ -1594,7 +1603,7 @@ namespace ts.server { * @param fileContent is a known version of the file content that is more up to date than the one on disk */ /*@internal*/ - getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: ServerHost) { + getOrCreateScriptInfo(uncheckedFileName: string, openedByClient: boolean, hostToQueryFileExistsOn: PartialSystem) { return this.getOrCreateScriptInfoForNormalizedPath( toNormalizedPath(uncheckedFileName), openedByClient, /*fileContent*/ undefined, /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, hostToQueryFileExistsOn @@ -1610,20 +1619,23 @@ namespace ts.server { // do not watch files with mixed content - server doesn't know how to interpret it if (!info.hasMixedContent) { const { fileName } = info; - info.fileWatcher = this.addFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, fileName, - (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind) + info.fileWatcher = this.watchFile( + this.host, + fileName, + (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind), + WatchType.ClosedScriptInfo ); } } - private stopWatchingScriptInfo(info: ScriptInfo, reason: WatcherCloseReason) { + private stopWatchingScriptInfo(info: ScriptInfo) { if (info.fileWatcher) { - this.closeFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, info.fileName, info.fileWatcher, reason); + info.fileWatcher.close(); info.fileWatcher = undefined; } } - getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: ServerHost) { + getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: PartialSystem) { const path = normalizedPathToPath(fileName, this.currentDirectory, this.toCanonicalFileName); let info = this.getScriptInfoForPath(path); if (!info) { @@ -1645,7 +1657,7 @@ namespace ts.server { } if (info) { if (openedByClient && !info.isScriptOpen()) { - this.stopWatchingScriptInfo(info, WatcherCloseReason.FileOpened); + this.stopWatchingScriptInfo(info); info.open(fileContent); if (hasMixedContent) { info.registerFileUpdate(); @@ -1693,39 +1705,6 @@ namespace ts.server { } } - /* @internal */ - closeFileWatcher(watchType: WatchType, project: Project, file: string, watcher: FileWatcher, reason: WatcherCloseReason) { - this.logger.info(`FileWatcher:: Close: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType} Reason: ${reason}`); - watcher.close(); - } - - /* @internal */ - addFileWatcher(watchType: WatchType, project: Project, file: string, cb: FileWatcherCallback) { - this.logger.info(`FileWatcher:: Added: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`); - return this.host.watchFile(file, (fileName, eventKind) => { - this.logger.info(`FileWatcher:: File ${FileWatcherEventKind[eventKind]}: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`); - cb(fileName, eventKind); - }); - } - - /* @internal */ - closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, flags: WatchDirectoryFlags, reason: WatcherCloseReason) { - const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; - this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Close: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType} Reason: ${reason}`); - watcher.close(); - } - - /* @internal */ - addDirectoryWatcher(watchType: WatchType, project: Project, directory: string, cb: ServerDirectoryWatcherCallback, flags: WatchDirectoryFlags) { - const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0; - this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Added: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType}`); - return this.host.watchDirectory(directory, fileName => { - const path = toNormalizedPath(getNormalizedAbsolutePath(fileName, directory)); - this.logger.info(`DirectoryWatcher:: EventOn: ${directory} Trigger: ${fileName} Path: ${path} Project: ${project.getProjectName()} WatchType: ${watchType}`); - cb(path); - }, recursive); - } - closeLog() { this.logger.close(); } diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 44b9893bc3d..b758cd796bf 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -4,110 +4,6 @@ /// namespace ts.server { - /*@internal*/ - export class CachedServerHost implements ServerHost { - args: string[]; - newLine: string; - useCaseSensitiveFileNames: boolean; - - private readonly cachedPartialSystem: CachedPartialSystem; - - readonly trace: (s: string) => void; - readonly realpath?: (path: string) => string; - - constructor(private readonly host: ServerHost) { - this.args = host.args; - this.newLine = host.newLine; - this.useCaseSensitiveFileNames = host.useCaseSensitiveFileNames; - if (host.trace) { - this.trace = s => host.trace(s); - } - if (this.host.realpath) { - this.realpath = path => this.host.realpath(path); - } - this.cachedPartialSystem = createCachedPartialSystem(host); - } - - write(s: string) { - return this.host.write(s); - } - - writeFile(fileName: string, data: string, writeByteOrderMark?: boolean) { - this.cachedPartialSystem.writeFile(fileName, data, writeByteOrderMark); - } - - resolvePath(path: string) { - return this.host.resolvePath(path); - } - - createDirectory(path: string) { - Debug.fail(`Why is createDirectory called on the cached server for ${path}`); - } - - getExecutingFilePath() { - return this.host.getExecutingFilePath(); - } - - getCurrentDirectory() { - return this.cachedPartialSystem.getCurrentDirectory(); - } - - exit(exitCode?: number) { - Debug.fail(`Why is exit called on the cached server: ${exitCode}`); - } - - getEnvironmentVariable(name: string) { - Debug.fail(`Why is getEnvironmentVariable called on the cached server: ${name}`); - return this.host.getEnvironmentVariable(name); - } - - getDirectories(rootDir: string) { - return this.cachedPartialSystem.getDirectories(rootDir); - } - - readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] { - return this.cachedPartialSystem.readDirectory(rootDir, extensions, excludes, includes, depth); - } - - fileExists(fileName: string): boolean { - return this.cachedPartialSystem.fileExists(fileName); - } - - directoryExists(dirPath: string) { - return this.cachedPartialSystem.directoryExists(dirPath); - } - - readFile(path: string, encoding?: string): string { - return this.host.readFile(path, encoding); - } - - addOrDeleteFileOrFolder(fileOrFolder: NormalizedPath, fileOrFolderPath: Path) { - return this.cachedPartialSystem.addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - } - - addOrDeleteFile(file: string, path: Path, eventKind: FileWatcherEventKind) { - return this.cachedPartialSystem.addOrDeleteFile(file, path, eventKind); - } - - clearCache() { - return this.cachedPartialSystem.clearCache(); - } - - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]) { - return this.host.setTimeout(callback, ms, ...args); - } - clearTimeout(timeoutId: any) { - return this.host.clearTimeout(timeoutId); - } - setImmediate(callback: (...args: any[]) => void, ...args: any[]) { - this.host.setImmediate(callback, ...args); - } - clearImmediate(timeoutId: any) { - this.host.clearImmediate(timeoutId); - } - - } - export class LSHost implements LanguageServiceHost, ModuleResolutionHost { /*@internal*/ compilationSettings: CompilerOptions; @@ -124,21 +20,26 @@ namespace ts.server { * file system entries as we would anyways be watching files in the project (so safe to cache) */ /*@internal*/ - host: ServerHost; + host: PartialSystem; - constructor(host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) { + constructor(host: PartialSystem, private project: Project, private readonly cancellationToken: HostCancellationToken) { this.host = host; this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); - if (host.trace) { - this.trace = s => host.trace(s); + const serverHost = this.getServerHost(); + if (serverHost.trace) { + this.trace = s => serverHost.trace(s); } - if (this.host.realpath) { - this.realpath = path => this.host.realpath(path); + if (serverHost.realpath) { + this.realpath = path => serverHost.realpath(path); } } + private getServerHost() { + return this.project.projectService.host; + } + dispose() { this.project = undefined; this.host = undefined; @@ -173,7 +74,7 @@ namespace ts.server { } getDefaultLibFileName() { - const nodeModuleBinDir = getDirectoryPath(normalizePath(this.host.getExecutingFilePath())); + const nodeModuleBinDir = getDirectoryPath(normalizePath(this.getServerHost().getExecutingFilePath())); return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings)); } @@ -207,7 +108,7 @@ namespace ts.server { } resolvePath(path: string): string { - return this.host.resolvePath(path); + return this.getServerHost().resolvePath(path); } fileExists(file: string): boolean { diff --git a/src/server/project.ts b/src/server/project.ts index cce0fc5b510..ec58e4cb88d 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -198,7 +198,7 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - host: ServerHost) { + host: PartialSystem) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -216,7 +216,7 @@ namespace ts.server { this.resolutionCache = createResolutionCache( fileName => this.projectService.toPath(fileName), () => this.compilerOptions, - (failedLookupLocation, failedLookupLocationPath, containingFile, name) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath, containingFile, name), + (failedLookupLocation, failedLookupLocationPath) => this.watchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath), s => this.projectService.logger.info(s), this.getProjectName(), () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined @@ -233,17 +233,24 @@ namespace ts.server { this.markAsDirty(); } - private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path, containingFile: string, name: string) { + private watchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path) { + return this.projectService.watchFile( + this.projectService.host, + failedLookupLocation, + (fileName, eventKind) => this.onFailedLookupLocationChanged(fileName, eventKind, failedLookupLocationPath), + WatchType.FailedLookupLocation, + this + ); + } + + private onFailedLookupLocationChanged(fileName: string, eventKind: FileWatcherEventKind, failedLookupLocationPath: Path) { // There is some kind of change in the failed lookup location, update the program - return this.projectService.addFileWatcher(WatchType.FailedLookupLocation, this, failedLookupLocation, (fileName, eventKind) => { - this.projectService.logger.info(`Watcher: FailedLookupLocations: Status: ${FileWatcherEventKind[eventKind]}: Location: ${failedLookupLocation}, containingFile: ${containingFile}, name: ${name}`); - if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); - } - this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); - }); + if (this.projectKind === ProjectKind.Configured) { + (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, failedLookupLocationPath, eventKind); + } + this.resolutionCache.invalidateResolutionOfChangedFailedLookupLocation(failedLookupLocationPath); + this.markAsDirty(); + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } private setInternalCompilerOptionsForEmittingJsFiles() { @@ -387,9 +394,7 @@ namespace ts.server { // Clean up file watchers waiting for missing files if (this.missingFilesMap) { - clearMap(this.missingFilesMap, (missingFilePath, fileWatcher) => { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose); - }); + clearMap(this.missingFilesMap, closeFileWatcher); this.missingFilesMap = undefined; } @@ -698,10 +703,7 @@ namespace ts.server { this.program, this.missingFilesMap || (this.missingFilesMap = createMap()), // Watch the missing files - missingFilePath => this.addMissingFileWatcher(missingFilePath), - // Files that are no longer missing (e.g. because they are no longer required) - // should no longer be watched. - (missingFilePath, fileWatcher) => this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded) + missingFilePath => this.addMissingFileWatcher(missingFilePath) ); } @@ -726,30 +728,29 @@ namespace ts.server { } private addMissingFileWatcher(missingFilePath: Path) { - const fileWatcher = this.projectService.addFileWatcher( - WatchType.MissingFilePath, this, missingFilePath, + const fileWatcher = this.projectService.watchFile( + this.projectService.host, + missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedServerHost).addOrDeleteFile(fileName, missingFilePath, eventKind); + (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { this.missingFilesMap.delete(missingFilePath); - this.closeMissingFileWatcher(missingFilePath, fileWatcher, WatcherCloseReason.FileCreated); + fileWatcher.close(); // When a missing file is created, we should update the graph. this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } - } + }, + WatchType.MissingFilePath, + this ); return fileWatcher; } - private closeMissingFileWatcher(missingFilePath: Path, fileWatcher: FileWatcher, reason: WatcherCloseReason) { - this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, reason); - } - isWatchedMissingFile(path: Path) { return this.missingFilesMap && this.missingFilesMap.has(path); } @@ -942,7 +943,7 @@ namespace ts.server { } removeRoot(info: ScriptInfo) { - this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.NotNeeded); + this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info); super.removeRoot(info); if (this._isJsInferredProject && info.isJavaScript()) { if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) { @@ -968,7 +969,7 @@ namespace ts.server { } close() { - forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info, WatcherCloseReason.ProjectClose)); + forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info)); super.close(); } @@ -1014,8 +1015,8 @@ namespace ts.server { compilerOptions: CompilerOptions, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, - cachedServerHost: CachedServerHost) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedServerHost); + cachedPartialSystem: PartialSystem) { + super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedPartialSystem); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); } @@ -1034,8 +1035,8 @@ namespace ts.server { } /*@internal*/ - getCachedServerHost() { - return this.lsHost.host as CachedServerHost; + getCachedPartialSystem() { + return this.lsHost.host as CachedPartialSystem; } getConfigFilePath() { @@ -1167,29 +1168,21 @@ namespace ts.server { this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), wildcardDirectories, // Create new directory watcher - (directory, flags) => this.projectService.addDirectoryWatcher( - WatchType.WildcardDirectories, this, directory, + (directory, flags) => this.projectService.watchDirectory( + this.projectService.host, + directory, path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), - flags - ), - // Close directory watcher - (directory, wildcardDirectoryWatcher, flagsChanged) => this.closeWildcardDirectoryWatcher( - directory, wildcardDirectoryWatcher, flagsChanged ? WatcherCloseReason.RecursiveChanged : WatcherCloseReason.NotNeeded + flags, + WatchType.WildcardDirectories, + this ) ); } - private closeWildcardDirectoryWatcher(directory: string, { watcher, flags }: WildcardDirectoryWatcher, closeReason: WatcherCloseReason) { - this.projectService.closeDirectoryWatcher(WatchType.WildcardDirectories, this, directory, watcher, flags, closeReason); - } - /*@internal*/ - stopWatchingWildCards(reason: WatcherCloseReason) { + stopWatchingWildCards() { if (this.directoriesWatchedForWildcards) { - clearMap( - this.directoriesWatchedForWildcards, - (directory, wildcardDirectoryWatcher) => this.closeWildcardDirectoryWatcher(directory, wildcardDirectoryWatcher, reason) - ); + clearMap(this.directoriesWatchedForWildcards, closeFileWatcherOf); this.directoriesWatchedForWildcards = undefined; } } @@ -1202,26 +1195,24 @@ namespace ts.server { newTypeRoots, { // Create new watch - createNewValue: root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root, - path => this.projectService.onTypeRootFileChanged(this, path), WatchDirectoryFlags.None + createNewValue: root => this.projectService.watchDirectory( + this.projectService.host, + root, + path => this.projectService.onTypeRootFileChanged(this, path), + WatchDirectoryFlags.None, + WatchType.TypeRoot, + this ), // Close existing watch thats not needed any more - onDeleteValue: (directory, watcher) => this.projectService.closeDirectoryWatcher( - WatchType.TypeRoot, this, directory, watcher, WatchDirectoryFlags.None, WatcherCloseReason.NotNeeded - ) + onDeleteValue: closeFileWatcher } ); } /*@internal*/ - stopWatchingTypeRoots(reason: WatcherCloseReason) { + stopWatchingTypeRoots() { if (this.typeRootsWatchers) { - clearMap( - this.typeRootsWatchers, - (directory, watcher) => - this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this, - directory, watcher, WatchDirectoryFlags.None, reason) - ); + clearMap(this.typeRootsWatchers, closeFileWatcher); this.typeRootsWatchers = undefined; } } @@ -1230,12 +1221,12 @@ namespace ts.server { super.close(); if (this.configFileWatcher) { - this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose); + this.configFileWatcher.close(); this.configFileWatcher = undefined; } - this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose); - this.stopWatchingWildCards(WatcherCloseReason.ProjectClose); + this.stopWatchingTypeRoots(); + this.stopWatchingWildCards(); } addOpenRef() { diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 93781df95c0..c35f26e3d3c 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -237,7 +237,7 @@ namespace ts.server { detachAllProjects() { for (const p of this.containingProjects) { if (p.projectKind === ProjectKind.Configured) { - (p.lsHost.host as CachedServerHost).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + (p.lsHost.host as CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); } const isInfoRoot = p.isRoot(this); // detach is unnecessary since we'll clean the list of containing projects anyways