From 5387cde6aace7438ac296a3436d3ff74a9c5129d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 7 Jul 2020 12:34:38 -0700 Subject: [PATCH] Resolve only relative references tree in the project --- src/compiler/resolutionCache.ts | 27 ++++++- src/compiler/watchPublic.ts | 1 + src/server/project.ts | 9 ++- .../semanticOperationsOnSyntaxServer.ts | 78 ++++++++++++++++--- 4 files changed, 99 insertions(+), 16 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 6ad1f9a29c7aa..45512e38cd1fd 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -141,7 +141,21 @@ namespace ts { type GetResolutionWithResolvedFileName = (resolution: T) => R | undefined; - export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, logChangesWhenResolvingModule: boolean): ResolutionCache { + export enum ResolutionKind { + All, + RelativeReferences + } + + const noResolveResolvedModule: ResolvedModuleWithFailedLookupLocations = { + resolvedModule: undefined, + failedLookupLocations: [] + }; + const noResolveResolvedTypeReferenceDirective: ResolvedTypeReferenceDirectiveWithFailedLookupLocations = { + resolvedTypeReferenceDirective: undefined, + failedLookupLocations: [] + }; + + export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string | undefined, resolutionKind: ResolutionKind, logChangesWhenResolvingModule: boolean): ResolutionCache { let filesWithChangedSetOfUnresolvedImports: Path[] | undefined; let filesWithInvalidatedResolutions: Set | undefined; let filesWithInvalidatedNonRelativeUnresolvedImports: ReadonlyESMap | undefined; @@ -341,11 +355,12 @@ namespace ts { shouldRetryResolution: (t: T) => boolean; reusedNames?: readonly string[]; logChanges?: boolean; + noResolveResolution: T; } function resolveNamesWithLocalCache({ names, containingFile, redirectedReference, cache, perDirectoryCacheWithRedirects, - loader, getResolutionWithResolvedFileName, + loader, getResolutionWithResolvedFileName, noResolveResolution, shouldRetryResolution, reusedNames, logChanges }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { const path = resolutionHost.toPath(containingFile); @@ -382,7 +397,9 @@ namespace ts { resolution = resolutionInDirectory; } else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference); + resolution = resolutionKind === ResolutionKind.All || isExternalModuleNameRelative(name) ? + loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference) : + noResolveResolution; perDirectoryResolution.set(name, resolution); } resolutionsInFile.set(name, resolution); @@ -441,6 +458,7 @@ namespace ts { loader: resolveTypeReferenceDirective, getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, + noResolveResolution: noResolveResolvedTypeReferenceDirective, }); } @@ -455,7 +473,8 @@ namespace ts { getResolutionWithResolvedFileName: getResolvedModule, shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), reusedNames, - logChanges: logChangesWhenResolvingModule + logChanges: logChangesWhenResolvingModule, + noResolveResolution: noResolveResolvedModule, }); } diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 778b7907f0ab7..0e80454c405f7 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -320,6 +320,7 @@ namespace ts { configFileName ? getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : currentDirectory, + ResolutionKind.All, /*logChangesWhenResolvingModule*/ false ); // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names diff --git a/src/server/project.ts b/src/server/project.ts index 2edb225fee734..5a5233cd97588 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -281,10 +281,8 @@ namespace ts.server { this.languageServiceEnabled = true; if (projectService.syntaxOnly) { - this.compilerOptions.noResolve = true; this.compilerOptions.types = []; } - this.setInternalCompilerOptionsForEmittingJsFiles(); const host = this.projectService.host; if (this.projectService.logger.loggingEnabled()) { @@ -296,7 +294,12 @@ namespace ts.server { this.realpath = maybeBind(host, host.realpath); // Use the current directory as resolution root only if the project created using current directory string - this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true); + this.resolutionCache = createResolutionCache( + this, + currentDirectory && this.currentDirectory, + projectService.syntaxOnly ? ResolutionKind.RelativeReferences : ResolutionKind.All, + /*logChangesWhenResolvingModule*/ true + ); this.languageService = createLanguageService(this, this.documentRegistry, this.projectService.syntaxOnly); if (lastFileExceededProgramSize) { this.disableLanguageService(lastFileExceededProgramSize); diff --git a/src/testRunner/unittests/tsserver/semanticOperationsOnSyntaxServer.ts b/src/testRunner/unittests/tsserver/semanticOperationsOnSyntaxServer.ts index 09b6df7c42ae8..709ac156c98b2 100644 --- a/src/testRunner/unittests/tsserver/semanticOperationsOnSyntaxServer.ts +++ b/src/testRunner/unittests/tsserver/semanticOperationsOnSyntaxServer.ts @@ -3,35 +3,67 @@ namespace ts.projectSystem { function setup() { const file1: File = { path: `${tscWatch.projectRoot}/a.ts`, - content: `import { y } from "./b"; + content: `import { y, cc } from "./b"; +import { something } from "something"; class c { prop = "hello"; foo() { return this.prop; } }` }; const file2: File = { path: `${tscWatch.projectRoot}/b.ts`, - content: "export const y = 10;" + content: `export { cc } from "./c"; +import { something } from "something"; + export const y = 10;` + }; + const file3: File = { + path: `${tscWatch.projectRoot}/c.ts`, + content: `export const cc = 10;` + }; + const something: File = { + path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`, + content: "export const something = 10;" }; const configFile: File = { path: `${tscWatch.projectRoot}/tsconfig.json`, content: "{}" }; - const host = createServerHost([file1, file2, libFile, configFile]); + const host = createServerHost([file1, file2, file3, something, libFile, configFile]); const session = createSession(host, { syntaxOnly: true, useSingleInferredProject: true }); - return { host, session, file1, file2, configFile }; + return { host, session, file1, file2, file3, something, configFile }; } it("open files are added to inferred project even if config file is present and semantic operations succeed", () => { - const { host, session, file1, file2 } = setup(); + const { host, session, file1, file2, file3, something } = setup(); const service = session.getProjectService(); openFilesForSession([file1], session); checkNumberOfProjects(service, { inferredProjects: 1 }); const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // Import is not resolved + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]); // Relative import is resolved but not non relative verifyCompletions(); + verifyGoToDefToB(); + verifyGoToDefToC(); openFilesForSession([file2], session); checkNumberOfProjects(service, { inferredProjects: 1 }); - checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]); verifyCompletions(); + verifyGoToDefToB(); + verifyGoToDefToC(); + + openFilesForSession([file3], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]); + + openFilesForSession([something], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]); + + // Close open files and verify resolutions + closeFilesForSession([file3], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]); + + closeFilesForSession([file2], session); + checkNumberOfProjects(service, { inferredProjects: 1 }); + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]); function verifyCompletions() { assert.isTrue(project.languageServiceEnabled); @@ -62,6 +94,34 @@ class c { prop = "hello"; foo() { return this.prop; } }` source: undefined }; } + + function verifyGoToDefToB() { + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: protocolFileLocationFromSubstring(file1, "y") + }).response as protocol.DefinitionInfoAndBoundSpan; + assert.deepEqual(response, { + definitions: [{ + file: file2.path, + ...protocolTextSpanWithContextFromSubstring({ fileText: file2.content, text: "y", contextText: "export const y = 10;" }) + }], + textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "y" }) + }); + } + + function verifyGoToDefToC() { + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: protocolFileLocationFromSubstring(file1, "cc") + }).response as protocol.DefinitionInfoAndBoundSpan; + assert.deepEqual(response, { + definitions: [{ + file: file3.path, + ...protocolTextSpanWithContextFromSubstring({ fileText: file3.content, text: "cc", contextText: "export const cc = 10;" }) + }], + textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "cc" }) + }); + } }); it("throws on unsupported commands", () => { @@ -97,7 +157,7 @@ class c { prop = "hello"; foo() { return this.prop; } }` }); it("should not include auto type reference directives", () => { - const { host, session, file1 } = setup(); + const { host, session, file1, file2, file3 } = setup(); const atTypes: File = { path: `/node_modules/@types/somemodule/index.d.ts`, content: "export const something = 10;" @@ -107,7 +167,7 @@ class c { prop = "hello"; foo() { return this.prop; } }` openFilesForSession([file1], session); checkNumberOfProjects(service, { inferredProjects: 1 }); const project = service.inferredProjects[0]; - checkProjectActualFiles(project, [libFile.path, file1.path]); // Should not contain atTypes + checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]); // Should not contain atTypes }); }); }