From f72af3be60688fa30a2e918e8dc86c1fad882b8a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 28 Jun 2019 15:49:48 -0700 Subject: [PATCH] Verify the scenarios when d.ts directory of dependency doesnt exist --- src/compiler/program.ts | 14 ++++-- src/compiler/types.ts | 9 ++-- src/server/project.ts | 45 ++++++++++++++++--- src/services/services.ts | 4 +- src/services/types.ts | 2 +- .../unittests/tsserver/projectReferences.ts | 30 +++++++++++++ 6 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 27f5d25c847..37c2d6a9c83 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -826,8 +826,11 @@ namespace ts { if (!resolvedProjectReferences) { resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); } - if (host.setGetSourceOfProjectReferenceRedirect) { - host.setGetSourceOfProjectReferenceRedirect(getSourceOfProjectReferenceRedirect); + if (host.setResolvedProjectReferenceCallbacks) { + host.setResolvedProjectReferenceCallbacks({ + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); } if (rootNames.length) { for (const parsedRef of resolvedProjectReferences) { @@ -1226,8 +1229,11 @@ namespace ts { } if (projectReferences) { resolvedProjectReferences = projectReferences.map(parseProjectReferenceConfigFile); - if (host.setGetSourceOfProjectReferenceRedirect) { - host.setGetSourceOfProjectReferenceRedirect(getSourceOfProjectReferenceRedirect); + if (host.setResolvedProjectReferenceCallbacks) { + host.setResolvedProjectReferenceCallbacks({ + getSourceOfProjectReferenceRedirect, + forEachResolvedProjectReference + }); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 79a92cfa31d..3cf506b0d58 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5166,7 +5166,7 @@ namespace ts { /* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean; createHash?(data: string): string; getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; - /* @internal */ setGetSourceOfProjectReferenceRedirect?(getSource: GetSourceOfProjectReferenceRedirect): void; + /* @internal */ setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void; /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; // TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesn't use compilerHost as base @@ -5175,10 +5175,13 @@ namespace ts { /** true if --out otherwise source file name */ /*@internal*/ - export type SourceOfProjectReferenceRedirect = string | true ; + export type SourceOfProjectReferenceRedirect = string | true; /*@internal*/ - export type GetSourceOfProjectReferenceRedirect = (fileName: string) => SourceOfProjectReferenceRedirect | undefined; + interface ResolvedProjectReferenceCallbacks { + getSourceOfProjectReferenceRedirect(fileName: string): SourceOfProjectReferenceRedirect | undefined; + forEachResolvedProjectReference(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined; + } /* @internal */ export const enum TransformFlags { diff --git a/src/server/project.ts b/src/server/project.ts index c6c8ab84b20..04a5b91531a 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1482,7 +1482,8 @@ namespace ts.server { configFileWatcher: FileWatcher | undefined; private directoriesWatchedForWildcards: Map | undefined; readonly canonicalConfigFilePath: NormalizedPath; - private getSourceOfProjectReferenceRedirect: GetSourceOfProjectReferenceRedirect | undefined; + private projectReferenceCallbacks: ResolvedProjectReferenceCallbacks | undefined; + private mapOfDeclarationDirectories: Map | undefined; /* @internal */ pendingReload: ConfigFileProgramReloadLevel | undefined; @@ -1529,8 +1530,8 @@ namespace ts.server { } /* @internal */ - setGetSourceOfProjectReferenceRedirect(getSource: GetSourceOfProjectReferenceRedirect) { - this.getSourceOfProjectReferenceRedirect = getSource; + setResolvedProjectReferenceCallbacks(projectReferenceCallbacks: ResolvedProjectReferenceCallbacks) { + this.projectReferenceCallbacks = projectReferenceCallbacks; } /* @internal */ @@ -1538,13 +1539,42 @@ namespace ts.server { fileExists(file: string): boolean { // Project references go to source file instead of .d.ts file - if (this.languageServiceEnabled && this.getSourceOfProjectReferenceRedirect) { - const source = this.getSourceOfProjectReferenceRedirect(file); + if (this.useSourceInsteadOfReferenceRedirect() && this.projectReferenceCallbacks) { + const source = this.projectReferenceCallbacks.getSourceOfProjectReferenceRedirect(file); if (source) return isString(source) ? super.fileExists(source) : true; } return super.fileExists(file); } + directoryExists(path: string): boolean { + if (super.directoryExists(path)) return true; + if (!this.useSourceInsteadOfReferenceRedirect() || !this.projectReferenceCallbacks) return false; + + if (!this.mapOfDeclarationDirectories) { + this.mapOfDeclarationDirectories = createMap(); + this.projectReferenceCallbacks.forEachResolvedProjectReference(ref => { + if (!ref) return; + const out = ref.commandLine.options.outFile || ref.commandLine.options.outDir; + if (out) { + this.mapOfDeclarationDirectories!.set(getDirectoryPath(this.toPath(out)), true); + } + else { + // Set declaration's in different locations only, if they are next to source the directory present doesnt change + const declarationDir = ref.commandLine.options.declarationDir || ref.commandLine.options.outDir; + if (declarationDir) { + this.mapOfDeclarationDirectories!.set(this.toPath(declarationDir), true); + } + } + }); + } + const dirPath = this.toPath(path); + const dirPathWithTrailingDirectorySeparator = `${dirPath}${directorySeparator}`; + return !!forEachKey( + this.mapOfDeclarationDirectories, + declDirPath => dirPath === declDirPath || startsWith(declDirPath, dirPathWithTrailingDirectorySeparator) + ); + } + /** * If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph * @returns: true if set of files in the project stays the same and false - otherwise. @@ -1553,6 +1583,8 @@ namespace ts.server { this.isInitialLoadPending = returnFalse; const reloadLevel = this.pendingReload; this.pendingReload = ConfigFileProgramReloadLevel.None; + this.projectReferenceCallbacks = undefined; + this.mapOfDeclarationDirectories = undefined; let result: boolean; switch (reloadLevel) { case ConfigFileProgramReloadLevel.Partial: @@ -1567,7 +1599,6 @@ namespace ts.server { default: result = super.updateGraph(); } - this.getSourceOfProjectReferenceRedirect = undefined; this.projectService.sendProjectLoadingFinishEvent(this); this.projectService.sendProjectTelemetry(this); return result; @@ -1684,6 +1715,8 @@ namespace ts.server { this.stopWatchingWildCards(); this.projectErrors = undefined; this.configFileSpecs = undefined; + this.projectReferenceCallbacks = undefined; + this.mapOfDeclarationDirectories = undefined; super.close(); } diff --git a/src/services/services.ts b/src/services/services.ts index aa6ea119225..7de7934653e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1246,8 +1246,8 @@ namespace ts { return host.resolveTypeReferenceDirectives!(typeReferenceDirectiveNames, containingFile, redirectedReference); }; } - if (host.setGetSourceOfProjectReferenceRedirect) { - compilerHost.setGetSourceOfProjectReferenceRedirect = getSource => host.setGetSourceOfProjectReferenceRedirect!(getSource); + if (host.setResolvedProjectReferenceCallbacks) { + compilerHost.setResolvedProjectReferenceCallbacks = callbacks => host.setResolvedProjectReferenceCallbacks!(callbacks); } if (host.useSourceInsteadOfReferenceRedirect) { compilerHost.useSourceInsteadOfReferenceRedirect = () => host.useSourceInsteadOfReferenceRedirect!(); diff --git a/src/services/types.ts b/src/services/types.ts index 3c9509ca5e9..b4fc25e587f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -237,7 +237,7 @@ namespace ts { /* @internal */ getSourceFileLike?(fileName: string): SourceFileLike | undefined; /* @internal */ - setGetSourceOfProjectReferenceRedirect?(getSource: GetSourceOfProjectReferenceRedirect): void; + setResolvedProjectReferenceCallbacks?(callbacks: ResolvedProjectReferenceCallbacks): void; /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; } diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 5320cfe13bc..0b8b074f8e2 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -727,6 +727,36 @@ ${dependencyTs.content}`); /*afterActionDocumentPositionMapperNotEquals*/ undefined, /*useDepedencyChange*/ true ); + + it("when d.ts file is not generated", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([...openFiles, randomFile], session); + + const expectedClosedInfos = closedInfos.filter(f => f.toLowerCase() !== dtsPath && f.toLowerCase() !== dtsMapPath); + // If closed infos includes dts and dtsMap, watch dts since its not present + const expectedWatchedFiles = closedInfos.length === expectedClosedInfos.length ? + otherWatchedFiles : + otherWatchedFiles.concat(dtsPath); + // Main scenario action + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }) => { + assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); + verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); + verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); + }, /*dtsAbsent*/ true); + checkProject(session); + + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + verifyInfosWithRandom(session, host, openInfos, expectedClosedInfos, expectedWatchedFiles); + verifyDocumentPositionMapper(session, /*dependencyMap*/ undefined, /*documentPositionMapper*/ undefined); + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + }); } }