From b0fb73c47cc7202f2477a4140dd3e2303d6aefce Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 3 Apr 2018 13:53:19 -0700 Subject: [PATCH 1/7] Typings cache is internal data structure --- src/server/editorServices.ts | 3 ++- src/server/typingsCache.ts | 3 ++- tests/baselines/reference/api/tsserverlibrary.d.ts | 12 ------------ 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 1f8f9d3b21b..dc2fa086a93 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -312,7 +312,8 @@ namespace ts.server { export class ProjectService { - public readonly typingsCache: TypingsCache; + /*@internal*/ + readonly typingsCache: TypingsCache; private readonly documentRegistry: DocumentRegistry; diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index f2642230f2d..6418b5f9cbe 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -24,7 +24,7 @@ namespace ts.server { globalTypingsCacheLocation: undefined }; - class TypingsCacheEntry { + interface TypingsCacheEntry { readonly typeAcquisition: TypeAcquisition; readonly compilerOptions: CompilerOptions; readonly typings: SortedReadonlyArray; @@ -80,6 +80,7 @@ namespace ts.server { return !arrayIsEqualTo(imports1, imports2); } + /*@internal*/ export class TypingsCache { private readonly perProjectCache: Map = createMap(); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2e8e3f3871d..d102f467ade 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7602,17 +7602,6 @@ declare namespace ts.server { readonly globalTypingsCacheLocation: string; } const nullTypingsInstaller: ITypingsInstaller; - class TypingsCache { - private readonly installer; - private readonly perProjectCache; - constructor(installer: ITypingsInstaller); - isKnownTypesPackageName(name: string): boolean; - installPackage(options: InstallPackageOptionsWithProject): Promise; - getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray, forceRefresh: boolean): SortedReadonlyArray; - updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, newTypings: string[]): void; - deleteTypingsForProject(projectName: string): void; - onProjectClosed(project: Project): void; - } } declare namespace ts.server { enum ProjectKind { @@ -7960,7 +7949,6 @@ declare namespace ts.server { syntaxOnly?: boolean; } class ProjectService { - readonly typingsCache: TypingsCache; private readonly documentRegistry; /** * Container of all known scripts From c9479f7263fbd51f454da2b6363238200e0abca8 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 12 Apr 2018 14:55:22 -0700 Subject: [PATCH 2/7] Remove the specialized type UnresolvedImportsMap which is just a redirection and helps only in test only --- src/harness/unittests/typingsInstaller.ts | 6 +-- src/server/project.ts | 42 +++---------------- .../reference/api/tsserverlibrary.d.ts | 12 ------ 3 files changed, 9 insertions(+), 51 deletions(-) diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 1a19c98f477..5ec7a9d7a17 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -999,7 +999,7 @@ namespace ts.projectSystem { proj.updateGraph(); assert.deepEqual( - proj.getCachedUnresolvedImportsPerFile_TestOnly().get(f1.path), + proj.cachedUnresolvedImportsPerFile.get(f1.path), ["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"] ); @@ -1029,7 +1029,7 @@ namespace ts.projectSystem { const projectService = session.getProjectService(); checkNumberOfProjects(projectService, { inferredProjects: 1 }); const proj = projectService.inferredProjects[0]; - const version1 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion(); + const version1 = proj.lastCachedUnresolvedImportsList; // make a change that should not affect the structure of the program const changeRequest: server.protocol.ChangeRequest = { @@ -1047,7 +1047,7 @@ namespace ts.projectSystem { }; session.executeCommand(changeRequest); host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects - const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion(); + const version2 = proj.lastCachedUnresolvedImportsList; assert.notEqual(version1, version2, "set of unresolved imports should change"); }); diff --git a/src/server/project.ts b/src/server/project.ts index 1502d107dee..23583d9c71b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -55,34 +55,6 @@ namespace ts.server { projectErrors: ReadonlyArray; } - export class UnresolvedImportsMap { - readonly perFileMap = createMap>(); - private version = 0; - - public clear() { - this.perFileMap.clear(); - this.version = 0; - } - - public getVersion() { - return this.version; - } - - public remove(path: Path) { - this.perFileMap.delete(path); - this.version++; - } - - public get(path: Path) { - return this.perFileMap.get(path); - } - - public set(path: Path, value: ReadonlyArray) { - this.perFileMap.set(path, value); - this.version++; - } - } - export interface PluginCreateInfo { project: Project; languageService: LanguageService; @@ -116,8 +88,10 @@ namespace ts.server { private missingFilesMap: Map; private plugins: PluginModule[] = []; - private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap(); - private lastCachedUnresolvedImportsList: SortedReadonlyArray; + /*@internal*/ + cachedUnresolvedImportsPerFile = createMap>(); + /*@internal*/ + lastCachedUnresolvedImportsList: SortedReadonlyArray; private lastFileExceededProgramSize: string | undefined; @@ -181,10 +155,6 @@ namespace ts.server { return hasOneOrMoreJsAndNoTsFiles(this); } - public getCachedUnresolvedImportsPerFile_TestOnly() { - return this.cachedUnresolvedImportsPerFile; - } - public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} { const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules"))); log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`); @@ -742,7 +712,7 @@ namespace ts.server { else { this.resolutionCache.invalidateResolutionOfFile(info.path); } - this.cachedUnresolvedImportsPerFile.remove(info.path); + this.cachedUnresolvedImportsPerFile.delete(info.path); if (detachFromProject) { info.detachFromProject(this); @@ -812,7 +782,7 @@ namespace ts.server { for (const file of changedFiles) { // delete cached information for changed files - this.cachedUnresolvedImportsPerFile.remove(file); + this.cachedUnresolvedImportsPerFile.delete(file); } // update builder only if language service is enabled diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d102f467ade..29959a89d02 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7611,15 +7611,6 @@ declare namespace ts.server { } function allRootFilesAreJsOrDts(project: Project): boolean; function allFilesAreJsOrDts(project: Project): boolean; - class UnresolvedImportsMap { - readonly perFileMap: Map>; - private version; - clear(): void; - getVersion(): number; - remove(path: Path): void; - get(path: Path): ReadonlyArray; - set(path: Path, value: ReadonlyArray): void; - } interface PluginCreateInfo { project: Project; languageService: LanguageService; @@ -7652,8 +7643,6 @@ declare namespace ts.server { private externalFiles; private missingFilesMap; private plugins; - private cachedUnresolvedImportsPerFile; - private lastCachedUnresolvedImportsList; private lastFileExceededProgramSize; protected languageService: LanguageService; languageServiceEnabled: boolean; @@ -7688,7 +7677,6 @@ declare namespace ts.server { private readonly cancellationToken; isNonTsProject(): boolean; isJsOnlyProject(): boolean; - getCachedUnresolvedImportsPerFile_TestOnly(): UnresolvedImportsMap; static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {}; isKnownTypesPackageName(name: string): boolean; installPackage(options: InstallPackageOptions): Promise; From 35abe268242207c58558d3e7d6077ffe09e2f779 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 12 Apr 2018 15:21:20 -0700 Subject: [PATCH 3/7] Force new typings resolution only if there are more or less script infos in the project. This helps in reducing number of forced typing installation requests We anyways use changes in unresolved import array to determine if we need to enqueue new typing request Hence there is no need to soley rely on hasChanges from updateGraph which just indicates that we didnt reused the program (that does not mean new files were added to the program or changes in unresolved imports) --- src/compiler/core.ts | 7 ++++--- src/server/project.ts | 6 +++++- src/server/scriptInfo.ts | 8 +++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index cdf68cb2dfa..b427c689c96 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2987,18 +2987,19 @@ namespace ts { } /** Remove the *first* occurrence of `item` from the array. */ - export function unorderedRemoveItem(array: T[], item: T): void { + export function unorderedRemoveItem(array: T[], item: T) { unorderedRemoveFirstItemWhere(array, element => element === item); } /** Remove the *first* element satisfying `predicate`. */ - function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean): void { + function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) { for (let i = 0; i < array.length; i++) { if (predicate(array[i])) { unorderedRemoveItemAt(array, i); - break; + return true; } } + return false; } export type GetCanonicalFileName = (fileName: string) => string; diff --git a/src/server/project.ts b/src/server/project.ts index 23583d9c71b..47bfb03a189 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -92,6 +92,8 @@ namespace ts.server { cachedUnresolvedImportsPerFile = createMap>(); /*@internal*/ lastCachedUnresolvedImportsList: SortedReadonlyArray; + /*@internal*/ + hasMoreOrLessScriptInfos = false; private lastFileExceededProgramSize: string | undefined; @@ -777,6 +779,8 @@ namespace ts.server { this.resolutionCache.startRecordingFilesWithChangedResolutions(); let hasChanges = this.updateGraphWorker(); + const hasMoreOrLessScriptInfos = this.hasMoreOrLessScriptInfos; + this.hasMoreOrLessScriptInfos = false; const changedFiles: ReadonlyArray = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray; @@ -803,7 +807,7 @@ namespace ts.server { this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result); } - const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasChanges); + const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessScriptInfos); if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) { this.typingFiles = cachedTypings; this.markAsDirty(); diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index db56973d796..074fde298aa 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -304,6 +304,7 @@ namespace ts.server { const isNew = !this.isAttached(project); if (isNew) { this.containingProjects.push(project); + project.hasMoreOrLessScriptInfos = true; if (!project.getCompilerOptions().preserveSymlinks) { this.ensureRealPath(); } @@ -328,19 +329,24 @@ namespace ts.server { return; case 1: if (this.containingProjects[0] === project) { + project.hasMoreOrLessScriptInfos = true; this.containingProjects.pop(); } break; case 2: if (this.containingProjects[0] === project) { + project.hasMoreOrLessScriptInfos = true; this.containingProjects[0] = this.containingProjects.pop(); } else if (this.containingProjects[1] === project) { + project.hasMoreOrLessScriptInfos = true; this.containingProjects.pop(); } break; default: - unorderedRemoveItem(this.containingProjects, project); + if (unorderedRemoveItem(this.containingProjects, project)) { + project.hasMoreOrLessScriptInfos = true; + } break; } } From 60b19f5782b87af71a2ca567da9c3f9d50638df2 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 12 Apr 2018 16:47:40 -0700 Subject: [PATCH 4/7] Invalidate the unresolved import resolutions when typing files are set This has 3 changes: 1. In updateGraph when enqueue the typing installation request (depending on unresolved imports) 2. When ActionSet event is received, invalidate only files with unresolved imports and resolve those. 3. When ActionInvalidate event is received, typing installer has detected some change in global typing cache location, so just enqueue a new typing installation request. This will repeat the cycle of setting correct typings and pickiing unresolved imports --- src/compiler/resolutionCache.ts | 20 ++++- .../unittests/tsserverProjectSystem.ts | 1 - src/harness/unittests/typingsInstaller.ts | 75 ++++++++++++++++++- src/server/editorServices.ts | 10 +-- src/server/project.ts | 71 ++++++++++++------ src/server/typingsCache.ts | 16 ++-- .../reference/api/tsserverlibrary.d.ts | 5 +- 7 files changed, 153 insertions(+), 45 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index c2b96649cf2..2cd39068de1 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -10,6 +10,7 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; removeResolutionsOfFile(filePath: Path): void; + setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map): void; createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; startCachingPerDirectoryResolution(): void; @@ -74,6 +75,7 @@ namespace ts { export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string, logChangesWhenResolvingModule: boolean): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; + let filesWithInvalidatedNonRelativeUnresolvedImports: Map | undefined; let allFilesHaveInvalidatedResolution = false; const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); @@ -122,6 +124,7 @@ namespace ts { resolveTypeReferenceDirectives, removeResolutionsOfFile, invalidateResolutionOfFile, + setFilesWithInvalidatedNonRelativeUnresolvedImports, createHasInvalidatedResolution, updateTypeRootsWatch, closeTypeRootsWatch, @@ -173,7 +176,8 @@ namespace ts { } const collected = filesWithInvalidatedResolutions; filesWithInvalidatedResolutions = undefined; - return path => collected && collected.has(path); + return path => (collected && collected.has(path)) || + (filesWithInvalidatedNonRelativeUnresolvedImports && filesWithInvalidatedNonRelativeUnresolvedImports.has(path)); } function clearPerDirectoryResolutions() { @@ -184,6 +188,7 @@ namespace ts { function finishCachingPerDirectoryResolution() { allFilesHaveInvalidatedResolution = false; + filesWithInvalidatedNonRelativeUnresolvedImports = undefined; directoryWatchesOfFailedLookups.forEach((watcher, path) => { if (watcher.refCount === 0) { directoryWatchesOfFailedLookups.delete(path); @@ -237,13 +242,15 @@ namespace ts { const resolvedModules: R[] = []; const compilerOptions = resolutionHost.getCompilationSettings(); - + const hasInvalidatedNonRelativeUnresolvedImport = logChanges && filesWithInvalidatedNonRelativeUnresolvedImports && filesWithInvalidatedNonRelativeUnresolvedImports.has(path); const seenNamesInFile = createMap(); for (const name of names) { let resolution = resolutionsInFile.get(name); // Resolution is valid if it is present and not invalidated if (!seenNamesInFile.has(name) && - allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated) { + allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated || + // If the name is unresolved import that was invalidated, recalculate + (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && !getResolutionWithResolvedFileName(resolution))) { const existingResolution = resolution; const resolutionInDirectory = perDirectoryResolution.get(name); if (resolutionInDirectory) { @@ -284,7 +291,7 @@ namespace ts { if (oldResolution === newResolution) { return true; } - if (!oldResolution || !newResolution || oldResolution.isInvalidated) { + if (!oldResolution || !newResolution) { return false; } const oldResult = getResolutionWithResolvedFileName(oldResolution); @@ -577,6 +584,11 @@ namespace ts { ); } + function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: Map) { + Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); + filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; + } + function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) { let isChangedFailedLookupLocation: (location: string) => boolean; if (isCreatingWatchedDirectory) { diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index b167ed21b94..10912b4fb87 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -7294,7 +7294,6 @@ namespace ts.projectSystem { const host = createServerHost(files); const session = createSession(host); const projectService = session.getProjectService(); - debugger; session.executeCommandSeq({ command: protocol.CommandTypes.Open, arguments: { diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts index 5ec7a9d7a17..b6d5a20ef9a 100644 --- a/src/harness/unittests/typingsInstaller.ts +++ b/src/harness/unittests/typingsInstaller.ts @@ -1006,7 +1006,7 @@ namespace ts.projectSystem { installer.installAll(/*expectedCount*/ 1); }); - it("should recompute resolutions after typings are installed", () => { + it("cached unresolved typings are not recomputed if program structure did not change", () => { const host = createServerHost([]); const session = createSession(host); const f = { @@ -1048,7 +1048,7 @@ namespace ts.projectSystem { session.executeCommand(changeRequest); host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects const version2 = proj.lastCachedUnresolvedImportsList; - assert.notEqual(version1, version2, "set of unresolved imports should change"); + assert.strictEqual(version1, version2, "set of unresolved imports should change"); }); it("expired cache entry (inferred project, should install typings)", () => { @@ -1621,4 +1621,75 @@ namespace ts.projectSystem { assert.deepEqual(commands, expectedCommands, "commands"); }); }); + + describe("recomputing resolutions of unresolved imports", () => { + const globalTypingsCacheLocation = "/tmp"; + const appPath = "/a/b/app.js" as Path; + const foooPath = "/a/b/node_modules/fooo/index.d.ts"; + function verifyResolvedModuleOfFooo(project: server.Project) { + const foooResolution = project.getLanguageService().getProgram().getSourceFileByPath(appPath).resolvedModules.get("fooo"); + assert.equal(foooResolution.resolvedFileName, foooPath); + return foooResolution; + } + + function verifyUnresolvedImportResolutions(appContents: string, typingNames: string[], typingFiles: FileOrFolder[]) { + const app: FileOrFolder = { + path: appPath, + content: `${appContents}import * as x from "fooo";` + }; + const fooo: FileOrFolder = { + path: foooPath, + content: `export var x: string;` + }; + const host = createServerHost([app, fooo]); + const installer = new (class extends Installer { + constructor() { + super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("foo") }); + } + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { + executeCommand(this, host, typingNames, typingFiles, cb); + } + })(); + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.openClientFile(app.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + + const proj = projectService.inferredProjects[0]; + checkProjectActualFiles(proj, [app.path, fooo.path]); + const foooResolution1 = verifyResolvedModuleOfFooo(proj); + + installer.installAll(/*expectedCount*/ 1); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(proj, typingFiles.map(f => f.path).concat(app.path, fooo.path)); + const foooResolution2 = verifyResolvedModuleOfFooo(proj); + assert.strictEqual(foooResolution1, foooResolution2); + } + + it("correctly invalidate the resolutions with typing names", () => { + verifyUnresolvedImportResolutions('import * as a from "foo";', ["foo"], [{ + path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`, + content: "export function a(): void;" + }]); + }); + + it("correctly invalidate the resolutions with typing names that are trimmed", () => { + const fooAA: FileOrFolder = { + path: `${globalTypingsCacheLocation}/node_modules/foo/a/a.d.ts`, + content: "export function a (): void;" + }; + const fooAB: FileOrFolder = { + path: `${globalTypingsCacheLocation}/node_modules/foo/a/b.d.ts`, + content: "export function b (): void;" + }; + const fooAC: FileOrFolder = { + path: `${globalTypingsCacheLocation}/node_modules/foo/a/c.d.ts`, + content: "export function c (): void;" + }; + verifyUnresolvedImportResolutions(` + import * as a from "foo/a/a"; + import * as b from "foo/a/b"; + import * as c from "foo/a/c"; + `, ["foo"], [fooAA, fooAB, fooAC]); + }); + }); } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index dc2fa086a93..1271b98c1c7 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -524,13 +524,13 @@ namespace ts.server { } switch (response.kind) { case ActionSet: - project.resolutionCache.clear(); - this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings); + // Update the typing files and update the project + project.updateTypingFiles(this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings)); break; case ActionInvalidate: - project.resolutionCache.clear(); - this.typingsCache.deleteTypingsForProject(response.projectName); - break; + // Do not clear resolution cache, there was changes detected in typings, so enque typing request and let it get us correct results + this.typingsCache.enqueueInstallTypingsForProject(project, project.lastCachedUnresolvedImportsList, /*forceRefresh*/ true); + return; } this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project); } diff --git a/src/server/project.ts b/src/server/project.ts index 47bfb03a189..67469cc69d8 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -89,7 +89,18 @@ namespace ts.server { private plugins: PluginModule[] = []; /*@internal*/ + /** + * This is map from files to unresolved imports in it + * Maop does not contain entries for files that do not have unresolved imports + * This helps in containing the set of files to invalidate + */ cachedUnresolvedImportsPerFile = createMap>(); + + /** + * This is the set that has entry to true if file doesnt contain any unresolved import + */ + private filesWithNoUnresolvedImports = createMap(); + /*@internal*/ lastCachedUnresolvedImportsList: SortedReadonlyArray; /*@internal*/ @@ -143,7 +154,8 @@ namespace ts.server { /*@internal*/ hasChangedAutomaticTypeDirectiveNames = false; - private typingFiles: SortedReadonlyArray; + /*@internal*/ + typingFiles: SortedReadonlyArray = emptyArray; private readonly cancellationToken: ThrottledCancellationToken; @@ -554,6 +566,7 @@ namespace ts.server { this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; + this.filesWithNoUnresolvedImports = undefined; this.directoryStructureHost = undefined; // Clean up file watchers waiting for missing files @@ -714,6 +727,7 @@ namespace ts.server { else { this.resolutionCache.invalidateResolutionOfFile(info.path); } + this.filesWithNoUnresolvedImports.delete(info.path); this.cachedUnresolvedImportsPerFile.delete(info.path); if (detachFromProject) { @@ -735,16 +749,21 @@ namespace ts.server { } /* @internal */ - private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push, ambientModules: string[]) { + private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[] | undefined, ambientModules: string[]): string[] | undefined { + // No unresolve imports in this file + if (this.filesWithNoUnresolvedImports.has(file.path)) { + return result; + } + const cached = this.cachedUnresolvedImportsPerFile.get(file.path); if (cached) { // found cached result - use it and return for (const f of cached) { - result.push(f); + (result || (result = [])).push(f); } - return; + return result; } - let unresolvedImports: string[]; + let unresolvedImports: string[] | undefined; if (file.resolvedModules) { file.resolvedModules.forEach((resolvedModule, name) => { // pick unresolved non-relative names @@ -760,11 +779,17 @@ namespace ts.server { trimmed = trimmed.substr(0, i); } (unresolvedImports || (unresolvedImports = [])).push(trimmed); - result.push(trimmed); + (result || (result = [])).push(trimmed); } }); } - this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray); + if (unresolvedImports) { + this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports); + } + else { + this.filesWithNoUnresolvedImports.set(file.path, true); + } + return result; function isAmbientlyDeclaredModule(name: string) { return ambientModules.some(m => m === name); @@ -778,7 +803,7 @@ namespace ts.server { updateGraph(): boolean { this.resolutionCache.startRecordingFilesWithChangedResolutions(); - let hasChanges = this.updateGraphWorker(); + const hasChanges = this.updateGraphWorker(); const hasMoreOrLessScriptInfos = this.hasMoreOrLessScriptInfos; this.hasMoreOrLessScriptInfos = false; @@ -787,6 +812,7 @@ namespace ts.server { for (const file of changedFiles) { // delete cached information for changed files this.cachedUnresolvedImportsPerFile.delete(file); + this.filesWithNoUnresolvedImports.delete(file); } // update builder only if language service is enabled @@ -799,20 +825,15 @@ namespace ts.server { // (can reuse cached imports for files that were not changed) // 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch if (hasChanges || changedFiles.length) { - const result: string[] = []; + let result: string[] | undefined; const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName())); for (const sourceFile of this.program.getSourceFiles()) { - this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules); + result = this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules); } - this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result); + this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray; } - const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessScriptInfos); - if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) { - this.typingFiles = cachedTypings; - this.markAsDirty(); - hasChanges = this.updateGraphWorker() || hasChanges; - } + this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessScriptInfos); } else { this.lastCachedUnresolvedImportsList = undefined; @@ -824,6 +845,13 @@ namespace ts.server { return !hasChanges; } + /*@internal*/ + updateTypingFiles(typingFiles: SortedReadonlyArray) { + this.typingFiles = typingFiles; + // Invalidate files with unresolved imports + this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile); + } + /* @internal */ getCurrentProgram() { return this.program; @@ -959,15 +987,14 @@ namespace ts.server { setCompilerOptions(compilerOptions: CompilerOptions) { if (compilerOptions) { compilerOptions.allowNonTsExtensions = true; - if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) { - // reset cached unresolved imports if changes in compiler options affected module resolution - this.cachedUnresolvedImportsPerFile.clear(); - this.lastCachedUnresolvedImportsList = undefined; - } const oldOptions = this.compilerOptions; this.compilerOptions = compilerOptions; this.setInternalCompilerOptionsForEmittingJsFiles(); if (changesAffectModuleResolution(oldOptions, compilerOptions)) { + // reset cached unresolved imports if changes in compiler options affected module resolution + this.cachedUnresolvedImportsPerFile.clear(); + this.filesWithNoUnresolvedImports.clear(); + this.lastCachedUnresolvedImportsList = undefined; this.resolutionCache.clear(); } this.markAsDirty(); diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index 6418b5f9cbe..c255757481f 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -95,15 +95,14 @@ namespace ts.server { return this.installer.installPackage(options); } - getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray, forceRefresh: boolean): SortedReadonlyArray { + enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray, forceRefresh: boolean) { const typeAcquisition = project.getTypeAcquisition(); if (!typeAcquisition || !typeAcquisition.enable) { - return emptyArray; + return; } const entry = this.perProjectCache.get(project.getProjectName()); - const result: SortedReadonlyArray = entry ? entry.typings : emptyArray; if (forceRefresh || !entry || typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) || @@ -114,28 +113,25 @@ namespace ts.server { this.perProjectCache.set(project.getProjectName(), { compilerOptions: project.getCompilationSettings(), typeAcquisition, - typings: result, + typings: entry ? entry.typings : emptyArray, unresolvedImports, poisoned: true }); // something has been changed, issue a request to update typings this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); } - return result; } updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, newTypings: string[]) { + const typings = toSortedArray(newTypings); this.perProjectCache.set(projectName, { compilerOptions, typeAcquisition, - typings: toSortedArray(newTypings), + typings, unresolvedImports, poisoned: false }); - } - - deleteTypingsForProject(projectName: string) { - this.perProjectCache.delete(projectName); + return !typeAcquisition || !typeAcquisition.enable ? emptyArray : typings; } onProjectClosed(project: Project) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 29959a89d02..53c23b1d382 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7643,6 +7643,10 @@ declare namespace ts.server { private externalFiles; private missingFilesMap; private plugins; + /** + * This is the set that has entry to true if file doesnt contain any unresolved import + */ + private filesWithNoUnresolvedImports; private lastFileExceededProgramSize; protected languageService: LanguageService; languageServiceEnabled: boolean; @@ -7673,7 +7677,6 @@ declare namespace ts.server { * This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project */ private projectStateVersion; - private typingFiles; private readonly cancellationToken; isNonTsProject(): boolean; isJsOnlyProject(): boolean; From 82e9a7595b9c289875e15c706f608e913fce2fcb Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 13 Apr 2018 15:15:09 -0700 Subject: [PATCH 5/7] Invoked should be property on watchers map instead of local variable since watchers arent closed if they need to be reopened --- src/server/typingsInstaller/typingsInstaller.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts index 8d482241d1b..f79564f6f98 100644 --- a/src/server/typingsInstaller/typingsInstaller.ts +++ b/src/server/typingsInstaller/typingsInstaller.ts @@ -64,11 +64,13 @@ namespace ts.server.typingsInstaller { onRequestCompleted: RequestCompletedAction; } + type ProjectWatchers = Map & { isInvoked?: boolean; }; + export abstract class TypingsInstaller { private readonly packageNameToTypingLocation: Map = createMap(); private readonly missingTypingsSet: Map = createMap(); private readonly knownCachesSet: Map = createMap(); - private readonly projectWatchers = createMap>(); + private readonly projectWatchers = createMap(); private safeList: JsTyping.SafeList | undefined; readonly pendingRunRequests: PendingRequest[] = []; @@ -378,8 +380,8 @@ namespace ts.server.typingsInstaller { this.projectWatchers.set(projectName, watchers); } + watchers.isInvoked = false; // handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings - let isInvoked = false; const isLoggingEnabled = this.log.isEnabled(); mutateMap( watchers, @@ -392,11 +394,11 @@ namespace ts.server.typingsInstaller { } const watcher = this.installTypingHost.watchFile(file, (f, eventKind) => { if (isLoggingEnabled) { - this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${file}:: handler is already invoked '${isInvoked}'`); + this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${file}:: handler is already invoked '${watchers.isInvoked}'`); } - if (!isInvoked) { + if (!watchers.isInvoked) { + watchers.isInvoked = true; this.sendResponse({ projectName, kind: ActionInvalidate }); - isInvoked = true; } }, /*pollingInterval*/ 2000); return isLoggingEnabled ? { From d64f2483e4c8838ff7bc46fa106c69a05901769d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 17 Apr 2018 14:17:15 -0700 Subject: [PATCH 6/7] Update to respond to PR feedback --- src/compiler/core.ts | 2 +- src/compiler/resolutionCache.ts | 20 +++-- src/server/project.ts | 82 ++++++++----------- src/server/scriptInfo.ts | 10 +-- .../reference/api/tsserverlibrary.d.ts | 8 +- 5 files changed, 57 insertions(+), 65 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index b427c689c96..0e53ae2946d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2988,7 +2988,7 @@ namespace ts { /** Remove the *first* occurrence of `item` from the array. */ export function unorderedRemoveItem(array: T[], item: T) { - unorderedRemoveFirstItemWhere(array, element => element === item); + return unorderedRemoveFirstItemWhere(array, element => element === item); } /** Remove the *first* element satisfying `predicate`. */ diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 2cd39068de1..35e056546c0 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -10,7 +10,7 @@ namespace ts { invalidateResolutionOfFile(filePath: Path): void; removeResolutionsOfFile(filePath: Path): void; - setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map): void; + setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map>): void; createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution; startCachingPerDirectoryResolution(): void; @@ -75,7 +75,7 @@ namespace ts { export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string, logChangesWhenResolvingModule: boolean): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Map | undefined; - let filesWithInvalidatedNonRelativeUnresolvedImports: Map | undefined; + let filesWithInvalidatedNonRelativeUnresolvedImports: Map> | undefined; let allFilesHaveInvalidatedResolution = false; const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory()); @@ -168,6 +168,16 @@ namespace ts { return collected; } + function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path) { + if (!filesWithInvalidatedNonRelativeUnresolvedImports) { + return false; + } + + // Invalidated if file has unresolved imports + const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path); + return value && !!value.length; + } + function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution { if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) { // Any file asked would have invalidated resolution @@ -177,7 +187,7 @@ namespace ts { const collected = filesWithInvalidatedResolutions; filesWithInvalidatedResolutions = undefined; return path => (collected && collected.has(path)) || - (filesWithInvalidatedNonRelativeUnresolvedImports && filesWithInvalidatedNonRelativeUnresolvedImports.has(path)); + isFileWithInvalidatedNonRelativeUnresolvedImports(path); } function clearPerDirectoryResolutions() { @@ -242,7 +252,7 @@ namespace ts { const resolvedModules: R[] = []; const compilerOptions = resolutionHost.getCompilationSettings(); - const hasInvalidatedNonRelativeUnresolvedImport = logChanges && filesWithInvalidatedNonRelativeUnresolvedImports && filesWithInvalidatedNonRelativeUnresolvedImports.has(path); + const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path); const seenNamesInFile = createMap(); for (const name of names) { let resolution = resolutionsInFile.get(name); @@ -584,7 +594,7 @@ namespace ts { ); } - function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: Map) { + function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: Map>) { Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined); filesWithInvalidatedNonRelativeUnresolvedImports = filesMap; } diff --git a/src/server/project.ts b/src/server/project.ts index 67469cc69d8..653f2943d0a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -96,15 +96,10 @@ namespace ts.server { */ cachedUnresolvedImportsPerFile = createMap>(); - /** - * This is the set that has entry to true if file doesnt contain any unresolved import - */ - private filesWithNoUnresolvedImports = createMap(); - /*@internal*/ lastCachedUnresolvedImportsList: SortedReadonlyArray; /*@internal*/ - hasMoreOrLessScriptInfos = false; + private hasMoreOrLessFiles = false; private lastFileExceededProgramSize: string | undefined; @@ -136,10 +131,10 @@ namespace ts.server { */ private lastReportedVersion = 0; /** - * Current project structure version. + * Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one) * This property is changed in 'updateGraph' based on the set of files in program */ - private projectStructureVersion = 0; + private projectProgramVersion = 0; /** * Current version of the project state. It is changed when: * - new root file was added/removed @@ -566,7 +561,6 @@ namespace ts.server { this.resolutionCache.clear(); this.resolutionCache = undefined; this.cachedUnresolvedImportsPerFile = undefined; - this.filesWithNoUnresolvedImports = undefined; this.directoryStructureHost = undefined; // Clean up file watchers waiting for missing files @@ -727,7 +721,6 @@ namespace ts.server { else { this.resolutionCache.invalidateResolutionOfFile(info.path); } - this.filesWithNoUnresolvedImports.delete(info.path); this.cachedUnresolvedImportsPerFile.delete(info.path); if (detachFromProject) { @@ -749,19 +742,11 @@ namespace ts.server { } /* @internal */ - private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[] | undefined, ambientModules: string[]): string[] | undefined { - // No unresolve imports in this file - if (this.filesWithNoUnresolvedImports.has(file.path)) { - return result; - } - + private extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: string[]): ReadonlyArray { const cached = this.cachedUnresolvedImportsPerFile.get(file.path); if (cached) { - // found cached result - use it and return - for (const f of cached) { - (result || (result = [])).push(f); - } - return result; + // found cached result, return + return cached; } let unresolvedImports: string[] | undefined; if (file.resolvedModules) { @@ -779,23 +764,23 @@ namespace ts.server { trimmed = trimmed.substr(0, i); } (unresolvedImports || (unresolvedImports = [])).push(trimmed); - (result || (result = [])).push(trimmed); } }); } - if (unresolvedImports) { - this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports); - } - else { - this.filesWithNoUnresolvedImports.set(file.path, true); - } - return result; + + this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray); + return unresolvedImports || emptyArray; function isAmbientlyDeclaredModule(name: string) { return ambientModules.some(m => m === name); } } + /* @internal */ + setHasMoreOrLessFiles() { + this.hasMoreOrLessFiles = true; + } + /** * Updates set of files that contribute to this project * @returns: true if set of files in the project stays the same and false - otherwise. @@ -803,16 +788,15 @@ namespace ts.server { updateGraph(): boolean { this.resolutionCache.startRecordingFilesWithChangedResolutions(); - const hasChanges = this.updateGraphWorker(); - const hasMoreOrLessScriptInfos = this.hasMoreOrLessScriptInfos; - this.hasMoreOrLessScriptInfos = false; + const hasNewProgram = this.updateGraphWorker(); + const hasMoreOrLessFiles = this.hasMoreOrLessFiles; + this.hasMoreOrLessFiles = false; const changedFiles: ReadonlyArray = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray; for (const file of changedFiles) { // delete cached information for changed files this.cachedUnresolvedImportsPerFile.delete(file); - this.filesWithNoUnresolvedImports.delete(file); } // update builder only if language service is enabled @@ -824,25 +808,28 @@ namespace ts.server { // 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files // (can reuse cached imports for files that were not changed) // 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch - if (hasChanges || changedFiles.length) { + if (hasNewProgram || changedFiles.length) { let result: string[] | undefined; const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName())); for (const sourceFile of this.program.getSourceFiles()) { - result = this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules); + const unResolved = this.extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules); + if (unResolved !== emptyArray) { + (result || (result = [])).push(...unResolved); + } } this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray; } - this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessScriptInfos); + this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessFiles); } else { this.lastCachedUnresolvedImportsList = undefined; } - if (hasChanges) { - this.projectStructureVersion++; + if (hasNewProgram) { + this.projectProgramVersion++; } - return !hasChanges; + return !hasNewProgram; } /*@internal*/ @@ -878,9 +865,9 @@ namespace ts.server { // bump up the version if // - oldProgram is not set - this is a first time updateGraph is called // - newProgram is different from the old program and structure of the old program was not reused. - const hasChanges = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))); + const hasNewProgram = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))); this.hasChangedAutomaticTypeDirectiveNames = false; - if (hasChanges) { + if (hasNewProgram) { if (oldProgram) { for (const f of oldProgram.getSourceFiles()) { if (this.program.getSourceFileByPath(f.path)) { @@ -918,8 +905,8 @@ namespace ts.server { removed => this.detachScriptInfoFromProject(removed) ); const elapsed = timestamp() - start; - this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`); - return hasChanges; + this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasNewProgram} Elapsed: ${elapsed}ms`); + return hasNewProgram; } private detachScriptInfoFromProject(uncheckedFileName: string) { @@ -993,7 +980,6 @@ namespace ts.server { if (changesAffectModuleResolution(oldOptions, compilerOptions)) { // reset cached unresolved imports if changes in compiler options affected module resolution this.cachedUnresolvedImportsPerFile.clear(); - this.filesWithNoUnresolvedImports.clear(); this.lastCachedUnresolvedImportsList = undefined; this.resolutionCache.clear(); } @@ -1007,7 +993,7 @@ namespace ts.server { const info: protocol.ProjectVersionInfo = { projectName: this.getProjectName(), - version: this.projectStructureVersion, + version: this.projectProgramVersion, isInferred: this.projectKind === ProjectKind.Inferred, options: this.getCompilationSettings(), languageServiceDisabled: !this.languageServiceEnabled, @@ -1018,7 +1004,7 @@ namespace ts.server { // check if requested version is the same that we have reported last time if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) { // if current structure version is the same - return info without any changes - if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) { + if (this.projectProgramVersion === this.lastReportedVersion && !updatedFileNames) { return { info, projectErrors: this.getGlobalProjectErrors() }; } // compute and return the difference @@ -1041,7 +1027,7 @@ namespace ts.server { } }); this.lastReportedFileNames = currentFiles; - this.lastReportedVersion = this.projectStructureVersion; + this.lastReportedVersion = this.projectProgramVersion; return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() }; } else { @@ -1050,7 +1036,7 @@ namespace ts.server { const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f)); const allFiles = projectFileNames.concat(externalFiles); this.lastReportedFileNames = arrayToSet(allFiles); - this.lastReportedVersion = this.projectStructureVersion; + this.lastReportedVersion = this.projectProgramVersion; return { info, files: allFiles, projectErrors: this.getGlobalProjectErrors() }; } } diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index 074fde298aa..cc4ebd519e5 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -304,7 +304,7 @@ namespace ts.server { const isNew = !this.isAttached(project); if (isNew) { this.containingProjects.push(project); - project.hasMoreOrLessScriptInfos = true; + project.setHasMoreOrLessFiles(); if (!project.getCompilerOptions().preserveSymlinks) { this.ensureRealPath(); } @@ -329,23 +329,23 @@ namespace ts.server { return; case 1: if (this.containingProjects[0] === project) { - project.hasMoreOrLessScriptInfos = true; + project.setHasMoreOrLessFiles(); this.containingProjects.pop(); } break; case 2: if (this.containingProjects[0] === project) { - project.hasMoreOrLessScriptInfos = true; + project.setHasMoreOrLessFiles(); this.containingProjects[0] = this.containingProjects.pop(); } else if (this.containingProjects[1] === project) { - project.hasMoreOrLessScriptInfos = true; + project.setHasMoreOrLessFiles(); this.containingProjects.pop(); } break; default: if (unorderedRemoveItem(this.containingProjects, project)) { - project.hasMoreOrLessScriptInfos = true; + project.setHasMoreOrLessFiles(); } break; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 53c23b1d382..a81f1c66c22 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7643,10 +7643,6 @@ declare namespace ts.server { private externalFiles; private missingFilesMap; private plugins; - /** - * This is the set that has entry to true if file doesnt contain any unresolved import - */ - private filesWithNoUnresolvedImports; private lastFileExceededProgramSize; protected languageService: LanguageService; languageServiceEnabled: boolean; @@ -7666,10 +7662,10 @@ declare namespace ts.server { */ private lastReportedVersion; /** - * Current project structure version. + * Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one) * This property is changed in 'updateGraph' based on the set of files in program */ - private projectStructureVersion; + private projectProgramVersion; /** * Current version of the project state. It is changed when: * - new root file was added/removed From 7c5f5249ae0a7b5be78d4f3549860e532208073d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Wed, 18 Apr 2018 11:05:56 -0700 Subject: [PATCH 7/7] Renames as per PR feedback --- src/server/project.ts | 12 ++++++------ src/server/scriptInfo.ts | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index 653f2943d0a..92645cfca52 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -99,7 +99,7 @@ namespace ts.server { /*@internal*/ lastCachedUnresolvedImportsList: SortedReadonlyArray; /*@internal*/ - private hasMoreOrLessFiles = false; + private hasAddedorRemovedFiles = false; private lastFileExceededProgramSize: string | undefined; @@ -777,8 +777,8 @@ namespace ts.server { } /* @internal */ - setHasMoreOrLessFiles() { - this.hasMoreOrLessFiles = true; + onFileAddedOrRemoved() { + this.hasAddedorRemovedFiles = true; } /** @@ -789,8 +789,8 @@ namespace ts.server { this.resolutionCache.startRecordingFilesWithChangedResolutions(); const hasNewProgram = this.updateGraphWorker(); - const hasMoreOrLessFiles = this.hasMoreOrLessFiles; - this.hasMoreOrLessFiles = false; + const hasAddedorRemovedFiles = this.hasAddedorRemovedFiles; + this.hasAddedorRemovedFiles = false; const changedFiles: ReadonlyArray = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray; @@ -820,7 +820,7 @@ namespace ts.server { this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray; } - this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasMoreOrLessFiles); + this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles); } else { this.lastCachedUnresolvedImportsList = undefined; diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts index cc4ebd519e5..589975769b1 100644 --- a/src/server/scriptInfo.ts +++ b/src/server/scriptInfo.ts @@ -304,7 +304,7 @@ namespace ts.server { const isNew = !this.isAttached(project); if (isNew) { this.containingProjects.push(project); - project.setHasMoreOrLessFiles(); + project.onFileAddedOrRemoved(); if (!project.getCompilerOptions().preserveSymlinks) { this.ensureRealPath(); } @@ -329,23 +329,23 @@ namespace ts.server { return; case 1: if (this.containingProjects[0] === project) { - project.setHasMoreOrLessFiles(); + project.onFileAddedOrRemoved(); this.containingProjects.pop(); } break; case 2: if (this.containingProjects[0] === project) { - project.setHasMoreOrLessFiles(); + project.onFileAddedOrRemoved(); this.containingProjects[0] = this.containingProjects.pop(); } else if (this.containingProjects[1] === project) { - project.setHasMoreOrLessFiles(); + project.onFileAddedOrRemoved(); this.containingProjects.pop(); } break; default: if (unorderedRemoveItem(this.containingProjects, project)) { - project.setHasMoreOrLessFiles(); + project.onFileAddedOrRemoved(); } break; }