diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d73f1b6062..6e34c80a3c 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -218,6 +218,9 @@ namespace Harness.LanguageService { const snapshot = this.getScriptSnapshot(path); return snapshot.getText(0, snapshot.getLength()); } + getTypeRootsVersion() { + return 0; + } log(s: string): void { } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 34e438d37f..3f0670edcc 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -363,6 +363,15 @@ namespace ts.server { this.printProjects(); } + private onTypeRootFileChanged(project: ConfiguredProject, fileName: string) { + this.logger.info(`Type root file ${fileName} changed`); + this.throttledOperations.schedule(project.configFileName + " * type root", /*delay*/ 250, () => { + project.updateTypes(); + this.updateConfiguredProject(project); // TODO: Figure out why this is needed (should be redundant?) + this.refreshInferredProjects(); + }); + } + /** * 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 @@ -395,6 +404,8 @@ namespace ts.server { // For configured projects, the change is made outside the tsconfig file, and // it is not likely to affect the project for other files opened by the client. We can // just update the current project. + + this.logger.info("Updating configured project"); this.updateConfiguredProject(project); // Call refreshInferredProjects to clean up inferred projects we may have @@ -771,6 +782,7 @@ namespace ts.server { this.watchConfigDirectoryForProject(project, projectOptions); } project.watchWildcards((project, path) => this.onSourceFileInDirectoryChangedForConfiguredProject(project, path)); + project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path)); this.configuredProjects.push(project); return project; diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index f6530953a5..5b33770ce2 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -145,6 +145,10 @@ namespace ts.server { return this.project.getRootFilesLSHost(); } + getTypeRootsVersion() { + return this.project.typesVersion; + } + getScriptKind(fileName: string) { const info = this.project.getScriptInfoLSHost(fileName); return info && info.scriptKind; diff --git a/src/server/project.ts b/src/server/project.ts index 9b4c7619fb..01858b4cc6 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -69,6 +69,8 @@ namespace ts.server { protected projectErrors: Diagnostic[]; + public typesVersion = 0; + public isJsOnlyProject() { this.updateGraph(); return allFilesAreJsOrDts(this); @@ -153,6 +155,12 @@ namespace ts.server { return this.program.getSourceFileByPath(path); } + updateTypes() { + this.typesVersion++; + this.markAsDirty(); + this.updateGraph(); + } + close() { if (this.program) { // if we have a program - release all files that are enlisted in program @@ -569,6 +577,7 @@ namespace ts.server { private projectFileWatcher: FileWatcher; private directoryWatcher: FileWatcher; private directoriesWatchedForWildcards: Map; + private typeRootsWatchers: FileWatcher[]; /** Used for configured projects which may have multiple open roots */ openRefCount = 0; @@ -608,6 +617,16 @@ namespace ts.server { this.projectFileWatcher = this.projectService.host.watchFile(this.configFileName, _ => callback(this)); } + watchTypeRoots(callback: (project: ConfiguredProject, path: string) => void) { + const roots = this.getEffectiveTypeRoots(); + const watchers: FileWatcher[] = []; + for (const root of roots) { + this.projectService.logger.info(`Add type root watcher for: ${root}`); + watchers.push(this.projectService.host.watchDirectory(root, path => callback(this, path), /*recursive*/ false)); + } + this.typeRootsWatchers = watchers; + } + watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) { if (this.directoryWatcher) { return; @@ -651,6 +670,13 @@ namespace ts.server { this.projectFileWatcher.close(); } + if (this.typeRootsWatchers) { + for (const watcher of this.typeRootsWatchers) { + watcher.close(); + } + this.typeRootsWatchers = undefined; + } + for (const id in this.directoriesWatchedForWildcards) { this.directoriesWatchedForWildcards[id].close(); } @@ -667,6 +693,10 @@ namespace ts.server { this.openRefCount--; return this.openRefCount; } + + getEffectiveTypeRoots() { + return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || []; + } } export class ExternalProject extends Project { diff --git a/src/services/services.ts b/src/services/services.ts index 8c233c5698..4b6e27e6c6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1185,6 +1185,11 @@ namespace ts { readFile?(path: string, encoding?: string): string; fileExists?(path: string): boolean; + /* + * LS host can optionally implement these methods to support automatic updating when new type libraries are installed + */ + getTypeRootsVersion?(): number; + /* * LS host can optionally implement this method if it wants to be completely in charge of module name resolution. * if implementation is omitted then language service will use built-in module resolution logic and get answers to @@ -3099,6 +3104,7 @@ namespace ts { let ruleProvider: formatting.RulesProvider; let program: Program; let lastProjectVersion: string; + let lastTypesRootVersion = 0; const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(); const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken()); @@ -3215,6 +3221,13 @@ namespace ts { }; } + const typeRootsVersion = host.getTypeRootsVersion ? host.getTypeRootsVersion() : 0; + if (lastTypesRootVersion !== typeRootsVersion) { + log("TypeRoots version has changed; provide new program"); + program = undefined; + lastTypesRootVersion = typeRootsVersion; + } + const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings); const newProgram = createProgram(hostCache.getRootFileNames(), newSettings, compilerHost, program); diff --git a/src/services/shims.ts b/src/services/shims.ts index f107809f3d..f2ee5b4409 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -67,6 +67,7 @@ namespace ts { getProjectVersion?(): string; useCaseSensitiveFileNames?(): boolean; + getTypeRootsVersion?(): number; readDirectory(rootDir: string, extension: string, basePaths?: string, excludeEx?: string, includeFileEx?: string, includeDirEx?: string, depth?: number): string; readFile(path: string, encoding?: string): string; fileExists(path: string): boolean; @@ -358,6 +359,13 @@ namespace ts { return this.shimHost.getProjectVersion(); } + public getTypeRootsVersion(): number { + if (!this.shimHost.getTypeRootsVersion) { + return 0; + } + return this.shimHost.getTypeRootsVersion(); + } + public useCaseSensitiveFileNames(): boolean { return this.shimHost.useCaseSensitiveFileNames ? this.shimHost.useCaseSensitiveFileNames() : false; }