From 17565d84079cec98e86fbf36b35265cfaa020bd3 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 24 Aug 2017 11:50:27 -0700 Subject: [PATCH] Handle watches of missing directories and make project the module resolution host --- Jakefile.js | 1 - src/compiler/resolutionCache.ts | 85 +++--- src/compiler/sys.ts | 140 +++++---- src/compiler/utilities.ts | 4 +- src/compiler/watchedProgram.ts | 197 +++++++------ .../unittests/cachingInServerLSHost.ts | 2 +- .../unittests/tsserverProjectSystem.ts | 116 +++++--- src/harness/virtualFileSystemWithWatch.ts | 61 +++- src/server/editorServices.ts | 83 ++++-- src/server/lsHost.ts | 137 --------- src/server/project.ts | 277 +++++++++++------- src/server/scriptInfo.ts | 2 +- src/server/session.ts | 2 +- src/server/tsconfig.json | 1 - src/server/tsconfig.library.json | 1 - src/server/typingsCache.ts | 6 +- src/server/utilities.ts | 2 +- 17 files changed, 586 insertions(+), 531 deletions(-) delete mode 100644 src/server/lsHost.ts diff --git a/Jakefile.js b/Jakefile.js index 2dcba9f97e7..46a744445d3 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -149,7 +149,6 @@ var harnessSources = harnessCoreSources.concat([ "utilities.ts", "scriptVersionCache.ts", "scriptInfo.ts", - "lsHost.ts", "project.ts", "typingsCache.ts", "editorServices.ts", diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index dd03a055804..d1bd285019f 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -5,8 +5,6 @@ namespace ts { /** This is the cache of module/typedirectives resolution that can be retained across program */ export interface ResolutionCache { - setModuleResolutionHost(host: ModuleResolutionHost): void; - startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[]; @@ -14,8 +12,6 @@ namespace ts { resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; invalidateResolutionOfFile(filePath: Path): void; - onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path): boolean; - createHasInvalidatedResolution(): HasInvalidatedResolution; clear(): void; @@ -33,15 +29,17 @@ namespace ts { mapLocations: MultiMap; } - export function createResolutionCache( - toPath: (fileName: string) => Path, - getCompilerOptions: () => CompilerOptions, - watchDirectoryOfFailedLookupLocation: (directory: string) => FileWatcher, - log: (s: string) => void, - projectName?: string, - getGlobalCache?: () => string | undefined): ResolutionCache { + export interface ResolutionCacheHost extends ModuleResolutionHost { + toPath(fileName: string): Path; + getCompilationSettings(): CompilerOptions; + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback): FileWatcher; + onInvalidatedResolution(): void; + getCachedPartialSystem?(): CachedPartialSystem; + projectName?: string; + getGlobalCache?(): string | undefined; + } - let host: ModuleResolutionHost; + export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; @@ -52,23 +50,16 @@ namespace ts { const resolvedTypeReferenceDirectives = createMap>(); const directoryWatchesOfFailedLookups = createMap(); - return { - setModuleResolutionHost, startRecordingFilesWithChangedResolutions, finishRecordingFilesWithChangedResolutions, resolveModuleNames, resolveTypeReferenceDirectives, invalidateResolutionOfFile, - onFileAddOrRemoveInDirectoryOfFailedLookup, createHasInvalidatedResolution, clear }; - function setModuleResolutionHost(updatedHost: ModuleResolutionHost) { - host = updatedHost; - } - function clear() { // Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf); @@ -95,16 +86,16 @@ namespace ts { function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host); // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts - if (!getGlobalCache) { + if (!resolutionHost.getGlobalCache) { return primaryResult; } // otherwise try to load typings from @types - const globalCache = getGlobalCache(); + const globalCache = resolutionHost.getGlobalCache(); if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension))) { // create different collection of failed lookup locations for second pass // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache); + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, resolutionHost.projectName, compilerOptions, host, globalCache); if (resolvedModule) { return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as Array, failedLookupLocations) }; } @@ -123,12 +114,12 @@ namespace ts { getResultFileName: (result: R) => string | undefined, logChanges: boolean): R[] { - const path = toPath(containingFile); + const path = resolutionHost.toPath(containingFile); const currentResolutionsInFile = cache.get(path); const newResolutions: Map = createMap(); const resolvedModules: R[] = []; - const compilerOptions = getCompilerOptions(); + const compilerOptions = resolutionHost.getCompilationSettings(); for (const name of names) { // check if this is a duplicate entry in the list @@ -140,7 +131,7 @@ namespace ts { resolution = existingResolution; } else { - resolution = loader(name, containingFile, compilerOptions, host); + resolution = loader(name, containingFile, compilerOptions, resolutionHost); updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations); } newResolutions.set(name, resolution); @@ -214,12 +205,31 @@ namespace ts { const mapLocations = createMultiMap(); mapLocations.add(failedLookupLocationPath, failedLookupLocation); directoryWatchesOfFailedLookups.set(dirPath, { - watcher: watchDirectoryOfFailedLookupLocation(getDirectoryPath(failedLookupLocation)), + watcher: createDirectoryWatcher(getDirectoryPath(failedLookupLocation), dirPath), mapLocations }); } } + function createDirectoryWatcher(directory: string, dirPath: Path) { + return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => { + const fileOrFolderPath = resolutionHost.toPath(fileOrFolder); + if (resolutionHost.getCachedPartialSystem) { + // Since the file existance changed, update the sourceFiles cache + resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + } + + // If the location results in update to failed lookup, schedule program update + if (dirPath === fileOrFolderPath) { + onAddOrRemoveDirectoryOfFailedLookup(dirPath); + resolutionHost.onInvalidatedResolution(); + } + else if (onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { + resolutionHost.onInvalidatedResolution(); + } + }); + } + function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) { const dirPath = getDirectoryPath(failedLookupLocationPath); const watches = directoryWatchesOfFailedLookups.get(dirPath); @@ -234,13 +244,12 @@ namespace ts { function withFailedLookupLocations(failedLookupLocations: ReadonlyArray | undefined, fn: FailedLookupLocationAction, startIndex?: number) { if (failedLookupLocations) { for (let i = startIndex || 0; i < failedLookupLocations.length; i++) { - fn(failedLookupLocations[i], toPath(failedLookupLocations[i])); + fn(failedLookupLocations[i], resolutionHost.toPath(failedLookupLocations[i])); } } } function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray | undefined, existingFailedLookupLocations: ReadonlyArray | undefined) { - log(`Resolution cache: Updating...`); const index = existingFailedLookupLocations && failedLookupLocations ? findDiffIndex(failedLookupLocations, existingFailedLookupLocations) : 0; @@ -269,7 +278,7 @@ namespace ts { if (resolution && !resolution.isInvalidated) { const result = getResult(resolution); if (result) { - if (toPath(getResultFileName(result)) === deletedFilePath) { + if (resolutionHost.toPath(getResultFileName(result)) === deletedFilePath) { resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(path, true); } @@ -281,12 +290,13 @@ namespace ts { } function invalidateResolutionCacheOfChangedFailedLookupLocation( - failedLookupLocationPath: Path, - cache: Map>) { + cache: Map>, + isChangedFailedLookupLocation: (location: string) => boolean + ) { cache.forEach((value, containingFile) => { if (value) { value.forEach(resolution => { - if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) { + if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, isChangedFailedLookupLocation)) { // Mark the file as needing re-evaluation of module resolution instead of using it blindly. resolution.isInvalidated = true; (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap())).set(containingFile, true); @@ -306,10 +316,17 @@ namespace ts { const watches = directoryWatchesOfFailedLookups.get(dirPath); const isFailedLookupFile = watches.mapLocations.has(fileOrFolder); if (isFailedLookupFile) { - invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedModuleNames); - invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedTypeReferenceDirectives); + const isFileOrFolder: (location: string) => boolean = location => resolutionHost.toPath(location) === fileOrFolder; + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isFileOrFolder); + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isFileOrFolder); } return isFailedLookupFile; } + + function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) { + const isInDirPath: (location: string) => boolean = location => getDirectoryPath(resolutionHost.toPath(location)) === dirPath; + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath); + invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath); + } } } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 7a419be7c2d..5f3b8bd06d1 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -161,11 +161,10 @@ namespace ts { watcher.referenceCount += 1; return; } - watcher = _fs.watch( + watcher = fsWatchDirectory( dirPath || ".", - { persistent: true }, (eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath) - ); + ) as DirectoryWatcher; watcher.referenceCount = 1; dirWatchers.set(dirPath, watcher); return; @@ -232,6 +231,83 @@ namespace ts { const platform: string = _os.platform(); const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); + function fsWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher { + _fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged); + return { + close: () => _fs.unwatchFile(fileName, fileChanged) + }; + + function fileChanged(curr: any, prev: any) { + const isCurrZero = +curr.mtime === 0; + const isPrevZero = +prev.mtime === 0; + const created = !isCurrZero && isPrevZero; + const deleted = isCurrZero && !isPrevZero; + + const eventKind = created + ? FileWatcherEventKind.Created + : deleted + ? FileWatcherEventKind.Deleted + : FileWatcherEventKind.Changed; + + if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) { + return; + } + + callback(fileName, eventKind); + } + } + + function fsWatchDirectory(directoryName: string, callback: (eventName: string, relativeFileName: string) => void, recursive?: boolean): FileWatcher { + // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows + // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) + let options: any; + let watcher = !directoryExists(directoryName) ? + watchMissingDirectory() : + watchPresentDirectory(); + return { + close: () => { + watcher.close(); + } + }; + + function watchPresentDirectory(): FileWatcher { + if (options === undefined) { + if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) { + options = { persistent: true, recursive: !!recursive }; + } + else { + options = { persistent: true }; + } + } + + const dirWatcher = _fs.watch( + directoryName, + options, + callback + ); + dirWatcher.on("error", () => { + // Deleting file + watcher = watchMissingDirectory(); + // Call the callback for current directory + callback("rename", ""); + }); + return dirWatcher; + } + + function watchMissingDirectory(): FileWatcher { + return fsWatchFile(directoryName, (_fileName, eventKind) => { + if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) { + watcher.close(); + watcher = watchPresentDirectory(); + // Call the callback for current directory + // For now it could be callback for the inner directory creation, + // but just return current directory, better than current no-op + callback("rename", ""); + } + }); + } + } + function readFile(fileName: string, _encoding?: string): string | undefined { if (!fileExists(fileName)) { return undefined; @@ -349,7 +425,6 @@ namespace ts { return filter(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory)); } - const noOpFileWatcher: FileWatcher = { close: noop }; const nodeSystem: System = { args: process.argv.slice(2), newLine: _os.EOL, @@ -367,60 +442,21 @@ namespace ts { }; } else { - _fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged); - return { - close: () => _fs.unwatchFile(fileName, fileChanged) - }; - } - - function fileChanged(curr: any, prev: any) { - const isCurrZero = +curr.mtime === 0; - const isPrevZero = +prev.mtime === 0; - const created = !isCurrZero && isPrevZero; - const deleted = isCurrZero && !isPrevZero; - - const eventKind = created - ? FileWatcherEventKind.Created - : deleted - ? FileWatcherEventKind.Deleted - : FileWatcherEventKind.Changed; - - if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) { - return; - } - - callback(fileName, eventKind); + return fsWatchFile(fileName, callback, pollingInterval); } }, watchDirectory: (directoryName, callback, recursive) => { // Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) - let options: any; - if (!directoryExists(directoryName)) { - // do nothing if target folder does not exist - return noOpFileWatcher; - } - - if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) { - options = { persistent: true, recursive: !!recursive }; - } - else { - options = { persistent: true }; - } - - return _fs.watch( - directoryName, - options, - (eventName: string, relativeFileName: string) => { - // In watchDirectory we only care about adding and removing files (when event name is - // "rename"); changes made within files are handled by corresponding fileWatchers (when - // event name is "change") - if (eventName === "rename") { - // When deleting a file, the passed baseFileName is null - callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName))); - } + return fsWatchDirectory(directoryName, (eventName, relativeFileName) => { + // In watchDirectory we only care about adding and removing files (when event name is + // "rename"); changes made within files are handled by corresponding fileWatchers (when + // event name is "change") + if (eventName === "rename") { + // When deleting a file, the passed baseFileName is null + callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName))); } - ); + }, recursive); }, resolvePath: path => _path.resolve(path), fileExists, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 641caa0ce57..19748bfe0de 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3576,9 +3576,7 @@ namespace ts { 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); + return host.watchDirectory(directory, cb, recursive); } export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher { diff --git a/src/compiler/watchedProgram.ts b/src/compiler/watchedProgram.ts index 77c67325ef9..9058588ade1 100644 --- a/src/compiler/watchedProgram.ts +++ b/src/compiler/watchedProgram.ts @@ -245,7 +245,6 @@ namespace ts { const sourceFilesCache = createMap(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files - 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; @@ -257,20 +256,47 @@ namespace ts { watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty); const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost; - const host = configFileName ? createCachedPartialSystem(system) : system; + const partialSystem = configFileName ? createCachedPartialSystem(system) : system; if (configFileName) { configFileWatcher = watchFile(system, configFileName, scheduleProgramReload, writeLog); } - const currentDirectory = host.getCurrentDirectory(); - const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); - // Cache for the module resolution - const resolutionCache = createResolutionCache( - fileName => toPath(fileName), - () => compilerOptions, + const getCurrentDirectory = memoize(() => partialSystem.getCurrentDirectory()); + const realpath = system.realpath && ((path: string) => system.realpath(path)); + const getCachedPartialSystem = configFileName && (() => partialSystem as CachedPartialSystem); + const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); + let newLine = getNewLineCharacter(compilerOptions, system); + + const compilerHost: CompilerHost & ResolutionCacheHost = { + // Members for CompilerHost + getSourceFile: getVersionedSourceFile, + getSourceFileByPath: getVersionedSourceFileByPath, + getDefaultLibLocation, + getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), + writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, + getCurrentDirectory, + useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, + getCanonicalFileName, + getNewLine: () => newLine, + fileExists, + readFile, + trace, + directoryExists, + getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", + getDirectories, + realpath, + resolveTypeReferenceDirectives, + resolveModuleNames, + onReleaseOldSourceFile, + // Members for ResolutionCacheHost + toPath, + getCompilationSettings: () => compilerOptions, watchDirectoryOfFailedLookupLocation, - writeLog - ); + getCachedPartialSystem, + onInvalidatedResolution: scheduleProgramUpdate + }; + // Cache for the module resolution + const resolutionCache = createResolutionCache(compilerHost); // There is no extra check needed since we can just rely on the program to decide emit const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true); @@ -285,14 +311,15 @@ namespace ts { function synchronizeProgram() { writeLog(`Synchronizing program`); - hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); + if (hasChangedCompilerOptions) { + newLine = getNewLineCharacter(compilerOptions, system); + } + + const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(); if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) { return; } - // Create the compiler host - const compilerHost = createWatchedCompilerHost(compilerOptions); - resolutionCache.setModuleResolutionHost(compilerHost); if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) { resolutionCache.clear(); } @@ -300,6 +327,7 @@ namespace ts { beforeCompile(compilerOptions); // Compile the program + compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; program = createProgram(rootFileNames, compilerOptions, compilerHost, program); builder.onProgramUpdateGraph(program, hasInvalidatedResolution); @@ -309,7 +337,7 @@ namespace ts { // 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, // if there is already watcher for it (for missing files) - // At that point our watches were updated, hence now we know that these paths are not tracked and need to be removed + // At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed // so that at later time we have correct result of their presence for (const missingFilePath of missingFilePathsRequestedForRelease) { if (!missingFilesMap.has(missingFilePath)) { @@ -319,40 +347,12 @@ namespace ts { missingFilePathsRequestedForRelease = undefined; } - afterCompile(host, program, builder); + afterCompile(partialSystem, program, builder); reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes)); } - function createWatchedCompilerHost(options: CompilerOptions): CompilerHost { - const newLine = getNewLineCharacter(options, system); - const realpath = system.realpath && ((path: string) => system.realpath(path)); - - return { - getSourceFile: getVersionedSourceFile, - getSourceFileByPath: getVersionedSourceFileByPath, - getDefaultLibLocation, - getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), - writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { }, - getCurrentDirectory: memoize(() => host.getCurrentDirectory()), - useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, - getCanonicalFileName, - getNewLine: () => newLine, - fileExists, - readFile: fileName => system.readFile(fileName), - trace: (s: string) => system.write(s + newLine), - directoryExists: directoryName => host.directoryExists(directoryName), - getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "", - getDirectories: (path: string) => host.getDirectories(path), - realpath, - resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile), - resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false), - onReleaseOldSourceFile, - hasInvalidatedResolution - }; - } - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName); } function fileExists(fileName: string) { @@ -362,7 +362,31 @@ namespace ts { return !isString(hostSourceFileInfo); } - return host.fileExists(fileName); + return partialSystem.fileExists(fileName); + } + + function directoryExists(directoryName: string) { + return partialSystem.directoryExists(directoryName); + } + + function readFile(fileName: string) { + return system.readFile(fileName); + } + + function trace(s: string) { + return system.write(s + newLine); + } + + function getDirectories(path: string) { + return partialSystem.getDirectories(path); + } + + function resolveModuleNames(moduleNames: string[], containingFile: string) { + return resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false); + } + + function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) { + return resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); } function getDefaultLibLocation(): string { @@ -503,7 +527,7 @@ namespace ts { writeLog(`Reloading config file: ${configFileName}`); needsReload = false; - const cachedHost = host as CachedPartialSystem; + const cachedHost = partialSystem as CachedPartialSystem; cachedHost.clearCache(); const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic); rootFileNames = configParseResult.fileNames; @@ -548,26 +572,12 @@ namespace ts { function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { if (configFileName) { - (host as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); + (partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind); } } - function watchDirectoryOfFailedLookupLocation(directory: string) { - return watchDirectory(system, directory, onFileAddOrRemoveInDirectoryOfFailedLookup, WatchDirectoryFlags.None, writeLog); - } - - function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { - const fileOrFolderPath = toPath(fileOrFolder); - - if (configFileName) { - // Since the file existance changed, update the sourceFiles cache - (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - } - - // If the location results in update to failed lookup, schedule program update - if (resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { - scheduleProgramUpdate(); - } + function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) { + return watchDirectory(system, directory, cb, WatchDirectoryFlags.None, writeLog); } function watchMissingFilePath(missingFilePath: Path) { @@ -593,42 +603,45 @@ namespace ts { updateWatchingWildcardDirectories( watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), createMapFromTemplate(configFileWildCardDirectories), - watchWildCardDirectory + watchWildcardDirectory ); } - function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) { - return watchDirectory(system, directory, onFileAddOrRemoveInWatchedDirectory, flags, writeLog); - } + function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { + return watchDirectory( + system, + directory, + fileOrFolder => { + Debug.assert(!!configFileName); - function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) { - Debug.assert(!!configFileName); + const fileOrFolderPath = toPath(fileOrFolder); - const fileOrFolderPath = toPath(fileOrFolder); + // Since the file existance changed, update the sourceFiles cache + (partialSystem as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + removeSourceFile(fileOrFolderPath); - // Since the file existance changed, update the sourceFiles cache - (host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - removeSourceFile(fileOrFolderPath); + // If the the added or created file or folder is not supported file name, ignore the file + // But when watched directory is added/removed, we need to reload the file list + if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { + writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); + return; + } - // If a change was made inside "folder/file", node will trigger the callback twice: - // one with the fileName being "folder/file", and the other one with "folder". - // We don't respond to the second one. - if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) { - writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`); - return; - } + // Reload is pending, do the reload + if (!needsReload) { + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, partialSystem); + if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { + reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); + } + rootFileNames = result.fileNames; - // Reload is pending, do the reload - if (!needsReload) { - const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host); - if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) { - reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName)); - } - rootFileNames = result.fileNames; - - // Schedule Update the program - scheduleProgramUpdate(); - } + // Schedule Update the program + scheduleProgramUpdate(); + } + }, + flags, + writeLog + ); } function computeHash(data: string) { diff --git a/src/harness/unittests/cachingInServerLSHost.ts b/src/harness/unittests/cachingInServerLSHost.ts index b7b382e5a1d..1d22dc0417e 100644 --- a/src/harness/unittests/cachingInServerLSHost.ts +++ b/src/harness/unittests/cachingInServerLSHost.ts @@ -147,7 +147,7 @@ namespace ts { // setting compiler options discards module resolution cache fileExistsCalled = false; - const compilerOptions = ts.cloneCompilerOptions(project.getCompilerOptions()); + const compilerOptions = ts.cloneCompilerOptions(project.getCompilationSettings()); compilerOptions.target = ts.ScriptTarget.ES5; project.setCompilerOptions(compilerOptions); diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 3c394da4e31..9068afa98cf 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -321,6 +321,34 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } + function getPathsForTypesOrModules(base: string, rootPaths: string[], typesOrModules: string[], map: Map, rootDir?: string) { + while (1) { + forEach(rootPaths, r => { + const rp = combinePaths(base, r); + forEach(typesOrModules, tm => { + map.set(tm === "" ? rp : combinePaths(rp, tm), true); + }); + }); + const parentDir = getDirectoryPath(base); + if (base === rootDir || parentDir === base) { + break; + } + base = parentDir; + } + return map; + } + + function getNodeModulesWatchedDirectories(path: string, modules: string[], map = createMap()) { + forEach(modules, module => { + getPathsForTypesOrModules(path, ["node_modules"], ["", module, "@types", `@types/${module}`], map); + }); + return map; + } + + function getTypesWatchedDirectories(path: string, typeRoots: string[], types: string[], map = createMap()) { + return getPathsForTypesOrModules(path, typeRoots, types.concat(""), map, path); + } + describe("tsserverProjectSystem", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", @@ -357,8 +385,9 @@ namespace ts.projectSystem { checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); - const moduleLookupLocations = ["/a/b/c/module.ts", "/a/b/c/module.tsx"]; - checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path, ...moduleLookupLocations)); + checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); + checkWatchedDirectories(host, ["/a/b/c"], /*recursive*/ false); + checkWatchedDirectories(host, [], /*recursive*/ true); }); it("can handle tsconfig file name with difference casing", () => { @@ -3403,9 +3432,9 @@ namespace ts.projectSystem { checkProjectActualFiles(projectService.inferredProjects[0], [file4.path]); checkProjectActualFiles(projectService.inferredProjects[1], [file1.path, file2.path]); checkProjectActualFiles(projectService.inferredProjects[2], [file3.path]); - assert.equal(projectService.inferredProjects[0].getCompilerOptions().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[1].getCompilerOptions().target, ScriptTarget.ESNext); - assert.equal(projectService.inferredProjects[2].getCompilerOptions().target, ScriptTarget.ES2015); + assert.equal(projectService.inferredProjects[0].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[1].getCompilationSettings().target, ScriptTarget.ESNext); + assert.equal(projectService.inferredProjects[2].getCompilationSettings().target, ScriptTarget.ES2015); }); }); @@ -3895,13 +3924,13 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); let project = projectService.inferredProjects[0]; - let options = project.getCompilerOptions(); + let options = project.getCompilationSettings(); assert.isTrue(options.maxNodeModuleJsDepth === 2); // Assert the option sticks projectService.setCompilerOptionsForInferredProjects({ target: ScriptTarget.ES2016 }); project = projectService.inferredProjects[0]; - options = project.getCompilerOptions(); + options = project.getCompilationSettings(); assert.isTrue(options.maxNodeModuleJsDepth === 2); }); @@ -3921,15 +3950,15 @@ namespace ts.projectSystem { projectService.openClientFile(file1.path); checkNumberOfInferredProjects(projectService, 1); let project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); projectService.openClientFile(file2.path); project = projectService.inferredProjects[0]; - assert.isTrue(project.getCompilerOptions().maxNodeModuleJsDepth === 2); + assert.isTrue(project.getCompilationSettings().maxNodeModuleJsDepth === 2); projectService.closeClientFile(file2.path); project = projectService.inferredProjects[0]; - assert.isUndefined(project.getCompilerOptions().maxNodeModuleJsDepth); + assert.isUndefined(project.getCompilationSettings().maxNodeModuleJsDepth); }); }); @@ -4164,7 +4193,8 @@ namespace ts.projectSystem { describe("WatchDirectories for config file with", () => { function verifyWatchDirectoriesCaseSensitivity(useCaseSensitiveFileNames: boolean) { const frontendDir = "/Users/someuser/work/applications/frontend"; - const canonicalFrontendDir = useCaseSensitiveFileNames ? frontendDir : frontendDir.toLowerCase(); + const toCanonical: (s: string) => Path = useCaseSensitiveFileNames ? s => s as Path : s => s.toLowerCase() as Path; + const canonicalFrontendDir = toCanonical(frontendDir); const file1: FileOrFolder = { path: `${frontendDir}/src/app/utils/Analytic.ts`, content: "export class SomeClass { };" @@ -4181,6 +4211,8 @@ namespace ts.projectSystem { path: "/a/lib/lib.es2016.full.d.ts", content: libFile.content }; + const typeRoots = ["types", "node_modules/@types"]; + const types = ["node", "jest"]; const tsconfigFile: FileOrFolder = { path: `${frontendDir}/tsconfig.json`, content: JSON.stringify({ @@ -4194,16 +4226,10 @@ namespace ts.projectSystem { "noEmitOnError": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "types": [ - "node", - "jest" - ], + types, "noUnusedLocals": true, "outDir": "./compiled", - "typeRoots": [ - "types", - "node_modules/@types" - ], + typeRoots, "baseUrl": ".", "paths": { "*": [ @@ -4223,10 +4249,21 @@ namespace ts.projectSystem { const projectFiles = [file1, file2, es2016LibFile, tsconfigFile]; const host = createServerHost(projectFiles, { useCaseSensitiveFileNames }); const projectService = createProjectService(host); - const canonicalConfigPath = useCaseSensitiveFileNames ? tsconfigFile.path : tsconfigFile.path.toLowerCase(); + const canonicalConfigPath = toCanonical(tsconfigFile.path); const { configFileName } = projectService.openClientFile(file1.path); assert.equal(configFileName, tsconfigFile.path, `should find config`); checkNumberOfConfiguredProjects(projectService, 1); + const watchedModuleDirectories = arrayFrom( + getNodeModulesWatchedDirectories( + canonicalFrontendDir, + types, + getTypesWatchedDirectories( + canonicalFrontendDir, + typeRoots, + types + ) + ).keys() + ); const project = projectService.configuredProjects.get(canonicalConfigPath); verifyProjectAndWatchedDirectories(); @@ -4270,10 +4307,17 @@ namespace ts.projectSystem { verifyProjectAndWatchedDirectories(); callsTrackingHost.verifyNoHostCalls(); + function getFilePathIfOpen(f: FileOrFolder) { + const path = toCanonical(f.path); + const info = projectService.getScriptInfoForPath(toCanonical(f.path)); + return info && info.isScriptOpen() ? undefined : path; + } + function verifyProjectAndWatchedDirectories() { checkProjectActualFiles(project, map(projectFiles, f => f.path)); + checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfOpen)); checkWatchedDirectories(host, [`${canonicalFrontendDir}/src`], /*recursive*/ true); - checkWatchedDirectories(host, [`${canonicalFrontendDir}/types`, `${canonicalFrontendDir}/node_modules/@types`], /*recursive*/ false); + checkWatchedDirectories(host, watchedModuleDirectories, /*recursive*/ false); } } @@ -4328,7 +4372,7 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const { configFileName } = projectService.openClientFile(app.path); assert.equal(configFileName, tsconfigJson.path, `should find config`); - const watchedModuleLocations = getNodeModulesWatchedDirectories(appFolder, "lodash"); + const watchedModuleLocations = arrayFrom(getNodeModulesWatchedDirectories(appFolder, ["lodash"]).keys()); verifyProject(); let timeoutAfterReloadFs = timeoutDuringPartialInstallation; @@ -4406,7 +4450,7 @@ namespace ts.projectSystem { const lodashIndexPath = "/a/b/node_modules/@types/lodash/index.d.ts"; projectFiles.push(find(filesAndFoldersToAdd, f => f.path === lodashIndexPath)); - watchedModuleLocations.length = watchedModuleLocations.indexOf(lodashIndexPath); + watchedModuleLocations.length = indexOf(watchedModuleLocations, getDirectoryPath(lodashIndexPath)); // npm installation complete, timeout after reload fs timeoutAfterReloadFs = true; verifyAfterPartialOrCompleteNpmInstall(2); @@ -4429,32 +4473,10 @@ namespace ts.projectSystem { const projectFilePaths = map(projectFiles, f => f.path); checkProjectActualFiles(project, projectFilePaths); - const filesWatched = filter(projectFilePaths, p => p !== app.path).concat(watchedModuleLocations); + const filesWatched = filter(projectFilePaths, p => p !== app.path); checkWatchedFiles(host, filesWatched); checkWatchedDirectories(host, [appFolder], /*recursive*/ true); - checkWatchedDirectories(host, [], /*recursive*/ false); - } - - function getNodeModulesWatchedDirectories(path: string, module: string): string[] { - const nodeModulesDir = combinePaths(path, "node_modules/"); - const parentDir = getDirectoryPath(path); - const parentNodeModules = parentDir !== path ? getNodeModulesWatchedDirectories(parentDir, module) : []; - return [ - `${nodeModulesDir}${module}.ts`, - `${nodeModulesDir}${module}.tsx`, - `${nodeModulesDir}${module}.d.ts`, - `${nodeModulesDir}${module}/index.ts`, - `${nodeModulesDir}${module}/index.tsx`, - `${nodeModulesDir}${module}/index.d.ts`, - `${nodeModulesDir}@types/${module}.d.ts`, - `${nodeModulesDir}@types/${module}/index.d.ts`, - `${nodeModulesDir}@types/${module}/package.json`, - `${nodeModulesDir}${module}.js`, - `${nodeModulesDir}${module}.jsx`, - `${nodeModulesDir}${module}/package.json`, - `${nodeModulesDir}${module}/index.js`, - `${nodeModulesDir}${module}/index.jsx`, - ].concat(parentNodeModules); + checkWatchedDirectories(host, watchedModuleLocations, /*recursive*/ false); } } diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 44beb83922f..4b249c78e15 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -95,8 +95,35 @@ namespace ts.TestFSWithWatch { } } + function getDiffInKeys(map: Map, expectedKeys: string[]) { + if (map.size === expectedKeys.length) { + return ""; + } + const notInActual: string[] = []; + const duplicates: string[] = []; + const seen = createMap(); + forEach(expectedKeys, expectedKey => { + if (seen.has(expectedKey)) { + duplicates.push(expectedKey); + return; + } + seen.set(expectedKey, true); + if (!map.has(expectedKey)) { + notInActual.push(expectedKey); + } + }); + const inActualNotExpected: string[] = []; + map.forEach((_value, key) => { + if (!seen.has(key)) { + inActualNotExpected.push(key); + } + seen.set(key, true); + }); + return `\n\nNotInActual: ${notInActual}\nDuplicates: ${duplicates}\nInActualButNotInExpected: ${inActualNotExpected}`; + } + function checkMapKeys(caption: string, map: Map, expectedKeys: string[]) { - assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}`); + assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, expectedKeys)}`); for (const name of expectedKeys) { assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); } @@ -175,6 +202,8 @@ namespace ts.TestFSWithWatch { type TimeOutCallback = () => any; + export type TestFileWatcher = { cb: FileWatcherCallback; fileName: string; }; + export type TestDirectoryWatcher = { cb: DirectoryWatcherCallback; directoryName: string; }; export class TestServerHost implements server.ServerHost { args: string[] = []; @@ -186,9 +215,9 @@ namespace ts.TestFSWithWatch { private timeoutCallbacks = new Callbacks(); private immediateCallbacks = new Callbacks(); - readonly watchedDirectories = createMultiMap(); - readonly watchedDirectoriesRecursive = createMultiMap(); - readonly watchedFiles = createMultiMap(); + readonly watchedDirectories = createMultiMap(); + readonly watchedDirectoriesRecursive = createMultiMap(); + readonly watchedFiles = createMultiMap(); constructor(public withSafeList: boolean, public useCaseSensitiveFileNames: boolean, private executingFilePath: string, private currentDirectory: string, fileOrFolderList: FileOrFolder[], public readonly newLine = "\n") { this.getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); @@ -317,8 +346,8 @@ namespace ts.TestFSWithWatch { // Invoke directory and recursive directory watcher for the folder // Here we arent invoking recursive directory watchers for the base folders // since that is something we would want to do for both file as well as folder we are deleting - invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => cb(relativePath)); - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath)); } if (basePath !== fileOrFolder.path) { @@ -333,8 +362,7 @@ namespace ts.TestFSWithWatch { private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind) { const callbacks = this.watchedFiles.get(this.toPath(fileFullPath)); - const fileName = getBaseFileName(fileFullPath); - invokeWatcherCallbacks(callbacks, cb => cb(fileName, eventKind)); + invokeWatcherCallbacks(callbacks, ({ cb, fileName }) => cb(fileName, eventKind)); } private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) { @@ -346,16 +374,20 @@ namespace ts.TestFSWithWatch { */ private invokeDirectoryWatcher(folderFullPath: string, fileName: string) { const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName); - invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath)); this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName); } + private directoryCallback({ cb, directoryName }: TestDirectoryWatcher, relativePath: string) { + cb(combinePaths(directoryName, relativePath)); + } + /** * This will call the recursive directory watcher for this directory as well as all the base directories */ private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) { const relativePath = this.getRelativePathToDirectory(fullPath, fileName); - invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => cb(relativePath)); + invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(fullPath)), cb => this.directoryCallback(cb, relativePath)); const basePath = getDirectoryPath(fullPath); if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) { this.invokeRecursiveDirectoryWatcher(basePath, fileName); @@ -437,9 +469,13 @@ namespace ts.TestFSWithWatch { }); } - watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { + watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher { const path = this.toFullPath(directoryName); const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories; + const callback: TestDirectoryWatcher = { + cb, + directoryName + }; map.add(path, callback); return { referenceCount: 0, @@ -452,8 +488,9 @@ namespace ts.TestFSWithWatch { return Harness.mockHash(s); } - watchFile(fileName: string, callback: FileWatcherCallback) { + watchFile(fileName: string, cb: FileWatcherCallback) { const path = this.toFullPath(fileName); + const callback: TestFileWatcher = { fileName, cb }; this.watchedFiles.add(path, callback); return { close: () => this.watchedFiles.remove(path, callback) }; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 73912b3bd91..a85ebebec5e 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -3,7 +3,6 @@ /// /// /// -/// /// /// @@ -476,7 +475,6 @@ namespace ts.server { this.typingsCache.deleteTypingsForProject(response.projectName); break; } - project.markAsDirty(); this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); } @@ -528,6 +526,7 @@ namespace ts.server { /* @internal */ delayUpdateProjectGraphAndInferredProjectsRefresh(project: Project) { + project.markAsDirty(); this.delayUpdateProjectGraph(project); this.delayInferredProjectsRefresh(); } @@ -712,36 +711,56 @@ namespace ts.server { } } - /* @internal */ - onTypeRootFileChanged(project: ConfiguredProject, fileOrFolder: string) { - project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); - project.updateTypes(); - this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + /*@internal*/ + watchTypeRootDirectory(root: Path, project: ConfiguredProject) { + // TODO: This is not needed anymore with watches for failed lookup locations? + return this.watchDirectory( + this.host, + root, + fileOrFolder => { + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); + project.updateTypes(); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + }, + WatchDirectoryFlags.None, + WatchType.TypeRoot, + project + ); } /** - * This is the callback function when a watched directory has added or removed source code files. - * @param project the project that associates with this directory watcher - * @param fileName the absolute file name that changed in watched directory + * This is to watch whenever files are added or removed to the wildcard directories */ - /* @internal */ - onFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, fileOrFolder: string) { - project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, this.toPath(fileOrFolder)); - const configFilename = project.getConfigFilePath(); + /*@internal*/ + watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject) { + return this.watchDirectory( + this.host, + directory, + fileOrFolder => { + const fileOrFolderPath = this.toPath(fileOrFolder); + project.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); + const configFilename = project.getConfigFilePath(); - // If a change was made inside "folder/file", node will trigger the callback twice: - // one with the fileName being "folder/file", and the other one with "folder". - // We don't respond to the second one. - if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) { - this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); - return; - } + // If the the added or created file or folder is not supported file name, ignore the file + // But when watched directory is added/removed, we need to reload the file list + if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) { + this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`); + return; + } - const configFileSpecs = project.configFileSpecs; - 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); + // Reload is pending, do the reload + if (!project.pendingReload) { + const configFileSpecs = project.configFileSpecs; + const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilationSettings(), project.getCachedPartialSystem(), this.hostConfiguration.extraFileExtensions); + project.updateErrorOnNoInputFiles(result.fileNames.length !== 0); + this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader, /*clientFileName*/ undefined); + this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); + } + }, + flags, + WatchType.WildcardDirectories, + project + ); } private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { @@ -1253,7 +1272,7 @@ namespace ts.server { return findProjectByName(projectFileName, this.externalProjects); } - private convertConfigFileContentToProjectOptions(configFilename: string, cachedServerHost: PartialSystem) { + private convertConfigFileContentToProjectOptions(configFilename: string, cachedPartialSystem: CachedPartialSystem) { configFilename = normalizePath(configFilename); const configFileContent = this.host.readFile(configFilename); @@ -1265,7 +1284,7 @@ namespace ts.server { const errors = result.parseDiagnostics; const parsedCommandLine = parseJsonSourceFileConfigFileContent( result, - cachedServerHost, + cachedPartialSystem, getDirectoryPath(configFilename), /*existingOptions*/ {}, configFilename, @@ -1350,7 +1369,7 @@ namespace ts.server { const data: ProjectInfoTelemetryEventData = { projectId: this.host.createHash(projectKey), fileStats: countEachFileTypes(project.getScriptInfos()), - compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilerOptions()), + compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilationSettings()), typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()), extends: projectOptions && projectOptions.configHasExtendsProperty, files: projectOptions && projectOptions.configHasFilesProperty, @@ -1435,8 +1454,8 @@ namespace ts.server { const normalizedPath = toNormalizedPath(newRootFile); let scriptInfo: ScriptInfo | NormalizedPath; let path: Path; - // Use the project's lsHost so that it can use caching instead of reaching to disk for the query - if (!project.lsHost.fileExists(newRootFile)) { + // Use the project's fileExists so that it can use caching instead of reaching to disk for the query + if (!project.fileExists(newRootFile)) { path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName); const existingValue = projectRootFilesMap.get(path); if (isScriptInfo(existingValue)) { @@ -1448,7 +1467,7 @@ namespace ts.server { else { const scriptKind = propertyReader.getScriptKind(f); const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions); - scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.lsHost.host); + scriptInfo = this.getOrCreateScriptInfoForNormalizedPath(normalizedPath, /*openedByClient*/ clientFileName === newRootFile, /*fileContent*/ undefined, scriptKind, hasMixedContent, project.partialSystem); path = scriptInfo.path; // If this script info is not already a root add it if (!project.isRoot(scriptInfo)) { diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts deleted file mode 100644 index b758cd796bf..00000000000 --- a/src/server/lsHost.ts +++ /dev/null @@ -1,137 +0,0 @@ -/// -/// -/// -/// - -namespace ts.server { - export class LSHost implements LanguageServiceHost, ModuleResolutionHost { - /*@internal*/ - compilationSettings: CompilerOptions; - - readonly trace: (s: string) => void; - readonly realpath?: (path: string) => string; - - /*@internal*/ - hasInvalidatedResolution: HasInvalidatedResolution; - - /** - * This is the host that is associated with the project. This is normally same as projectService's host - * except in Configured projects where it is CachedServerHost so that we can cache the results of the - * file system entries as we would anyways be watching files in the project (so safe to cache) - */ - /*@internal*/ - host: PartialSystem; - - constructor(host: PartialSystem, private project: Project, private readonly cancellationToken: HostCancellationToken) { - this.host = host; - this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds); - - const serverHost = this.getServerHost(); - if (serverHost.trace) { - this.trace = s => serverHost.trace(s); - } - - if (serverHost.realpath) { - this.realpath = path => serverHost.realpath(path); - } - } - - private getServerHost() { - return this.project.projectService.host; - } - - dispose() { - this.project = undefined; - this.host = undefined; - } - - getNewLine() { - return this.host.newLine; - } - - getProjectVersion() { - return this.project.getProjectVersion(); - } - - getCompilationSettings() { - return this.compilationSettings; - } - - useCaseSensitiveFileNames() { - return this.host.useCaseSensitiveFileNames; - } - - getCancellationToken() { - return this.cancellationToken; - } - - resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { - return this.project.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); - } - - resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { - return this.project.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); - } - - getDefaultLibFileName() { - const nodeModuleBinDir = getDirectoryPath(normalizePath(this.getServerHost().getExecutingFilePath())); - return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings)); - } - - getScriptSnapshot(filename: string): IScriptSnapshot { - const scriptInfo = this.project.getScriptInfoLSHost(filename); - if (scriptInfo) { - return scriptInfo.getSnapshot(); - } - } - - getScriptFileNames() { - return this.project.getRootFilesLSHost(); - } - - getTypeRootsVersion() { - return this.project.typesVersion; - } - - getScriptKind(fileName: string) { - const info = this.project.getScriptInfoLSHost(fileName); - return info && info.scriptKind; - } - - getScriptVersion(filename: string) { - const info = this.project.getScriptInfoLSHost(filename); - return info && info.getLatestVersion(); - } - - getCurrentDirectory(): string { - return this.host.getCurrentDirectory(); - } - - resolvePath(path: string): string { - return this.getServerHost().resolvePath(path); - } - - fileExists(file: string): boolean { - // As an optimization, don't hit the disks for files we already know don't exist - // (because we're watching for their creation). - const path = this.project.projectService.toPath(file); - return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file); - } - - readFile(fileName: string): string | undefined { - return this.host.readFile(fileName); - } - - directoryExists(path: string): boolean { - return this.host.directoryExists(path); - } - - readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { - return this.host.readDirectory(path, extensions, exclude, include, depth); - } - - getDirectories(path: string): string[] { - return this.host.getDirectories(path); - } - } -} diff --git a/src/server/project.ts b/src/server/project.ts index 420d080b9f2..bf73d4d175c 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1,7 +1,7 @@ /// /// /// -/// +/// /// /// @@ -112,7 +112,7 @@ namespace ts.server { return value instanceof ScriptInfo; } - export abstract class Project { + export abstract class Project implements LanguageServiceHost, ModuleResolutionHost { private rootFiles: ScriptInfo[] = []; private rootFilesMap: Map = createMap(); private program: Program; @@ -127,11 +127,14 @@ namespace ts.server { public languageServiceEnabled = true; - /*@internal*/ - resolutionCache: ResolutionCache; + readonly trace?: (s: string) => void; + readonly realpath?: (path: string) => string; /*@internal*/ - lsHost: LSHost; + hasInvalidatedResolution: HasInvalidatedResolution; + + /*@internal*/ + resolutionCache: ResolutionCache; private builder: Builder; /** @@ -161,7 +164,7 @@ namespace ts.server { private typingFiles: SortedReadonlyArray; - public typesVersion = 0; + private typesVersion = 0; public isNonTsProject() { this.updateGraph(); @@ -190,7 +193,7 @@ namespace ts.server { } constructor( - private readonly projectName: string, + /*@internal*/readonly projectName: string, readonly projectKind: ProjectKind, readonly projectService: ProjectService, private documentRegistry: DocumentRegistry, @@ -198,7 +201,7 @@ namespace ts.server { languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, - host: PartialSystem) { + /*@internal*/public partialSystem: PartialSystem) { if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); @@ -211,52 +214,162 @@ namespace ts.server { } this.setInternalCompilerOptionsForEmittingJsFiles(); + const host = this.projectService.host; + if (host.trace) { + this.trace = s => host.trace(s); + } - this.lsHost = new LSHost(host, this, this.projectService.cancellationToken); - this.resolutionCache = createResolutionCache( - fileName => this.projectService.toPath(fileName), - () => this.compilerOptions, - directory => this.watchDirectoryOfFailedLookup(directory), - s => this.projectService.logger.info(s), - this.getProjectName(), - () => this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined - ); - this.lsHost.compilationSettings = this.compilerOptions; - this.resolutionCache.setModuleResolutionHost(this.lsHost); - - this.languageService = createLanguageService(this.lsHost, this.documentRegistry); + if (host.realpath) { + this.realpath = path => host.realpath(path); + } + this.languageService = createLanguageService(this, this.documentRegistry); if (!languageServiceEnabled) { this.disableLanguageService(); } - + this.resolutionCache = createResolutionCache(this); this.markAsDirty(); } - private watchDirectoryOfFailedLookup(directory: string) { + getCompilationSettings() { + return this.compilerOptions; + } + + getNewLine() { + return this.partialSystem.newLine; + } + + getProjectVersion() { + return this.projectStateVersion.toString(); + } + + getScriptFileNames() { + const result: string[] = []; + if (this.rootFiles) { + this.rootFilesMap.forEach((value, _path) => { + const f: ScriptInfo = isScriptInfo(value) && value; + if (this.languageServiceEnabled || (f && f.isScriptOpen())) { + // if language service is disabled - process only files that are open + result.push(f ? f.fileName : value as NormalizedPath); + } + }); + if (this.typingFiles) { + for (const f of this.typingFiles) { + result.push(f); + } + } + } + return result; + } + + private getScriptInfoLSHost(fileName: string) { + const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.partialSystem); + if (scriptInfo) { + const existingValue = this.rootFilesMap.get(scriptInfo.path); + if (existingValue !== undefined && existingValue !== scriptInfo) { + // This was missing path earlier but now the file exists. Update the root + this.rootFiles.push(scriptInfo); + this.rootFilesMap.set(scriptInfo.path, scriptInfo); + } + scriptInfo.attachToProject(this); + } + return scriptInfo; + } + + getScriptKind(fileName: string) { + const info = this.getScriptInfoLSHost(fileName); + return info && info.scriptKind; + } + + getScriptVersion(filename: string) { + const info = this.getScriptInfoLSHost(filename); + return info && info.getLatestVersion(); + } + + getScriptSnapshot(filename: string): IScriptSnapshot { + const scriptInfo = this.getScriptInfoLSHost(filename); + if (scriptInfo) { + return scriptInfo.getSnapshot(); + } + } + + getCancellationToken() { + return this.projectService.cancellationToken; + } + + getCurrentDirectory(): string { + return this.partialSystem.getCurrentDirectory(); + } + + getDefaultLibFileName() { + const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.host.getExecutingFilePath())); + return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilerOptions)); + } + + useCaseSensitiveFileNames() { + return this.partialSystem.useCaseSensitiveFileNames; + } + + readDirectory(path: string, extensions?: ReadonlyArray, exclude?: ReadonlyArray, include?: ReadonlyArray, depth?: number): string[] { + return this.partialSystem.readDirectory(path, extensions, exclude, include, depth); + } + + readFile(fileName: string): string | undefined { + return this.partialSystem.readFile(fileName); + } + + fileExists(file: string): boolean { + // As an optimization, don't hit the disks for files we already know don't exist + // (because we're watching for their creation). + const path = this.toPath(file); + return !this.isWatchedMissingFile(path) && this.partialSystem.fileExists(file); + } + + getTypeRootsVersion() { + return this.typesVersion; + } + + resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModuleFull[] { + return this.resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ true); + } + + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] { + return this.resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile); + } + + directoryExists(path: string): boolean { + return this.partialSystem.directoryExists(path); + } + + getDirectories(path: string): string[] { + return this.partialSystem.getDirectories(path); + } + + /*@internal*/ + toPath(fileName: string) { + return this.projectService.toPath(fileName); + } + + /*@internal*/ + watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) { return this.projectService.watchDirectory( this.projectService.host, directory, - fileOrFolder => this.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder), + cb, WatchDirectoryFlags.None, WatchType.FailedLookupLocation, this ); } - private onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) { - const fileOrFolderPath = this.projectService.toPath(fileOrFolder); + /*@internal*/ + onInvalidatedResolution() { + this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); + } - // There is some kind of change in the failed lookup location, update the program - if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath); - } - - // If the location results in update to failed lookup, schedule program update - if (this.resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) { - this.markAsDirty(); - this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); - } + /*@internal*/ + getGlobalCache() { + return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; } private setInternalCompilerOptionsForEmittingJsFiles() { @@ -326,10 +439,6 @@ namespace ts.server { return this.builder.getChangedProgramFiles(this.program); } - getProjectVersion() { - return this.projectStateVersion.toString(); - } - enableLanguageService() { if (this.languageServiceEnabled) { return; @@ -366,7 +475,6 @@ namespace ts.server { updateTypes() { this.typesVersion++; - this.markAsDirty(); } close() { @@ -395,8 +503,7 @@ namespace ts.server { this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; - this.lsHost.dispose(); - this.lsHost = undefined; + this.partialSystem = undefined; // Clean up file watchers waiting for missing files if (this.missingFilesMap) { @@ -410,11 +517,7 @@ namespace ts.server { } isClosed() { - return this.lsHost === undefined; - } - - getCompilerOptions() { - return this.compilerOptions; + return this.rootFiles === undefined; } hasRoots() { @@ -425,25 +528,6 @@ namespace ts.server { return this.rootFiles && this.rootFiles.map(info => info.fileName); } - getRootFilesLSHost() { - const result: string[] = []; - if (this.rootFiles) { - this.rootFilesMap.forEach((value, _path) => { - const f: ScriptInfo = isScriptInfo(value) && value; - if (this.languageServiceEnabled || (f && f.isScriptOpen())) { - // if language service is disabled - process only files that are open - result.push(f ? f.fileName : value as NormalizedPath); - } - }); - if (this.typingFiles) { - for (const f of this.typingFiles) { - result.push(f); - } - } - } - return result; - } - /*@internal*/ getRootFilesMap() { return this.rootFilesMap; @@ -622,7 +706,7 @@ namespace ts.server { */ updateGraph(): boolean { this.resolutionCache.startRecordingFilesWithChangedResolutions(); - this.lsHost.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution(); + this.hasInvalidatedResolution = this.resolutionCache.createHasInvalidatedResolution(); let hasChanges = this.updateGraphWorker(); @@ -659,7 +743,7 @@ namespace ts.server { // Note we are retaining builder so we can send events for project change if (this.builder) { if (this.languageServiceEnabled) { - this.builder.onProgramUpdateGraph(this.program, this.lsHost.hasInvalidatedResolution); + this.builder.onProgramUpdateGraph(this.program, this.hasInvalidatedResolution); } else { this.builder.clear(); @@ -720,7 +804,7 @@ namespace ts.server { // by the LSHost for files in the program when the program is retrieved above but // the program doesn't contain external files so this must be done explicitly. inserted => { - const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.lsHost.host); + const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false, this.partialSystem); scriptInfo.attachToProject(this); }, removed => { @@ -739,7 +823,7 @@ namespace ts.server { missingFilePath, (fileName, eventKind) => { if (this.projectKind === ProjectKind.Configured) { - (this.lsHost.host as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); + (this.partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, missingFilePath, eventKind); } if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) { @@ -747,7 +831,6 @@ namespace ts.server { fileWatcher.close(); // When a missing file is created, we should update the graph. - this.markAsDirty(); this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this); } }, @@ -757,28 +840,14 @@ namespace ts.server { return fileWatcher; } - isWatchedMissingFile(path: Path) { + private isWatchedMissingFile(path: Path) { return this.missingFilesMap && this.missingFilesMap.has(path); } - getScriptInfoLSHost(fileName: string) { - const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false, this.lsHost.host); - if (scriptInfo) { - const existingValue = this.rootFilesMap.get(scriptInfo.path); - if (existingValue !== undefined && existingValue !== scriptInfo) { - // This was missing path earlier but now the file exists. Update the root - this.rootFiles.push(scriptInfo); - this.rootFilesMap.set(scriptInfo.path, scriptInfo); - } - scriptInfo.attachToProject(this); - } - return scriptInfo; - } - getScriptInfoForNormalizedPath(fileName: NormalizedPath) { const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath( fileName, /*openedByClient*/ false, /*fileContent*/ undefined, - /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.lsHost.host + /*scriptKind*/ undefined, /*hasMixedContent*/ undefined, this.partialSystem ); if (scriptInfo && !scriptInfo.isAttached(this)) { return Errors.ThrowProjectDoesNotContainDocument(fileName, this); @@ -815,8 +884,6 @@ namespace ts.server { if (changesAffectModuleResolution(oldOptions, compilerOptions)) { this.resolutionCache.clear(); } - this.lsHost.compilationSettings = this.compilerOptions; - this.markAsDirty(); } } @@ -839,7 +906,7 @@ namespace ts.server { projectName: this.getProjectName(), version: this.projectStructureVersion, isInferred: this.projectKind === ProjectKind.Inferred, - options: this.getCompilerOptions(), + options: this.getCompilationSettings(), languageServiceDisabled: !this.languageServiceEnabled }; const updatedFileNames = this.updatedFileNames; @@ -913,7 +980,7 @@ namespace ts.server { setCompilerOptions(options?: CompilerOptions) { // Avoid manipulating the given options directly - const newOptions = options ? cloneCompilerOptions(options) : this.getCompilerOptions(); + const newOptions = options ? cloneCompilerOptions(options) : this.getCompilationSettings(); if (!newOptions) { return; } @@ -1042,7 +1109,7 @@ namespace ts.server { /*@internal*/ getCachedPartialSystem() { - return this.lsHost.host as CachedPartialSystem; + return this.partialSystem as CachedPartialSystem; } getConfigFilePath() { @@ -1051,7 +1118,7 @@ namespace ts.server { enablePlugins() { const host = this.projectService.host; - const options = this.getCompilerOptions(); + const options = this.getCompilationSettings(); if (!host.require) { this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded"); @@ -1113,7 +1180,7 @@ namespace ts.server { config: configEntry, project: this, languageService: this.languageService, - languageServiceHost: this.lsHost, + languageServiceHost: this, serverHost: this.projectService.host }; @@ -1174,14 +1241,7 @@ namespace ts.server { this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), wildcardDirectories, // Create new directory watcher - (directory, flags) => this.projectService.watchDirectory( - this.projectService.host, - directory, - path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path), - flags, - WatchType.WildcardDirectories, - this - ) + (directory, flags) => this.projectService.watchWildcardDirectory(directory as Path, flags, this), ); } @@ -1201,14 +1261,7 @@ namespace ts.server { newTypeRoots, { // Create new watch - createNewValue: root => this.projectService.watchDirectory( - this.projectService.host, - root, - path => this.projectService.onTypeRootFileChanged(this, path), - WatchDirectoryFlags.None, - WatchType.TypeRoot, - this - ), + createNewValue: root => this.projectService.watchTypeRootDirectory(root as Path, this), // Close existing watch thats not needed any more onDeleteValue: closeFileWatcher } @@ -1245,7 +1298,7 @@ namespace ts.server { } getEffectiveTypeRoots() { - return getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || []; + return getEffectiveTypeRoots(this.getCompilationSettings(), this.partialSystem) || []; } /*@internal*/ diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index c35f26e3d3c..d76f930f5ec 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 CachedPartialSystem).addOrDeleteFile(this.fileName, this.path, FileWatcherEventKind.Deleted); + (p.partialSystem 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 diff --git a/src/server/session.ts b/src/server/session.ts index 4b42aaba96a..b4f2b46f99c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1218,7 +1218,7 @@ namespace ts.server { result.push({ projectFileName: project.getProjectName(), fileNames: project.getCompileOnSaveAffectedFileList(info), - projectUsesOutFile: !!project.getCompilerOptions().outFile || !!project.getCompilerOptions().out + projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out }); } } diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index b0eb4965ea6..c838a7209d9 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -15,7 +15,6 @@ "utilities.ts", "scriptVersionCache.ts", "scriptInfo.ts", - "lsHost.ts", "typingsCache.ts", "project.ts", "editorServices.ts", diff --git a/src/server/tsconfig.library.json b/src/server/tsconfig.library.json index b59395fda27..aa3148d58a1 100644 --- a/src/server/tsconfig.library.json +++ b/src/server/tsconfig.library.json @@ -16,7 +16,6 @@ }, "files": [ "editorServices.ts", - "lsHost.ts", "project.ts", "protocol.ts", "scriptInfo.ts", diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 3ef37685da2..207824616a9 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -89,12 +89,12 @@ namespace ts.server { if (forceRefresh || !entry || typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) || - compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions) || + compilerOptionsChanged(project.getCompilationSettings(), entry.compilerOptions) || unresolvedImportsChanged(unresolvedImports, entry.unresolvedImports)) { // Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options. // instead it acts as a placeholder to prevent issuing multiple requests this.perProjectCache.set(project.getProjectName(), { - compilerOptions: project.getCompilerOptions(), + compilerOptions: project.getCompilationSettings(), typeAcquisition, typings: result, unresolvedImports, @@ -125,4 +125,4 @@ namespace ts.server { this.installer.onProjectClosed(project); } } -} \ No newline at end of file +} diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 3ae1dd1d71d..c596176ad86 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -50,7 +50,7 @@ namespace ts.server { return { projectName: project.getProjectName(), fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true), - compilerOptions: project.getCompilerOptions(), + compilerOptions: project.getCompilationSettings(), typeAcquisition, unresolvedImports, projectRootPath: getProjectRootPath(project),