diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index fb6adf59b26..13e6b9f280e 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -51,6 +51,7 @@ namespace ts { getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; projectName?: string; getGlobalCache?(): string | undefined; + globalCacheResolutionModuleName?(externalModuleName: string): string; writeLog(s: string): void; maxNumberOfFilesToIterateForInvalidation?: number; getCurrentProgram(): Program | undefined; @@ -270,7 +271,12 @@ namespace ts { if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(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, resolutionHost.projectName, compilerOptions, host, globalCache); + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache( + Debug.assertDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), + resolutionHost.projectName, + compilerOptions, + host, + globalCache); if (resolvedModule) { return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as string[], failedLookupLocations) }; } diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index 379ca6491a6..c2b2ad3f5b3 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -70,6 +70,10 @@ namespace ts.JsTyping { export const nodeCoreModules = arrayToSet(nodeCoreModuleList); + export function nonRelativeModuleNameForTypingCache(moduleName: string) { + return nodeCoreModules.has(moduleName) ? "node" : moduleName; + } + /** * A map of loose file names to library names that we are confident require typings */ @@ -150,7 +154,7 @@ namespace ts.JsTyping { // add typings for unresolved imports if (unresolvedImports) { const module = deduplicate( - unresolvedImports.map(moduleId => nodeCoreModules.has(moduleId) ? "node" : moduleId), + unresolvedImports.map(nonRelativeModuleNameForTypingCache), equateStringsCaseSensitive, compareStringsCaseSensitive); addInferredTypings(module, "Inferred typings from unresolved imports"); diff --git a/src/server/project.ts b/src/server/project.ts index e10a0988f71..a1a43a430f1 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -457,6 +457,9 @@ namespace ts.server { return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined; } + /*@internal*/ + globalCacheResolutionModuleName = JsTyping.nonRelativeModuleNameForTypingCache; + /*@internal*/ fileIsOpen(filePath: Path) { return this.projectService.openFiles.has(filePath); diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index 3a1aa4de64d..b02adbbd094 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -1781,6 +1781,70 @@ namespace ts.projectSystem { import * as c from "foo/a/c"; `, ["foo"], [fooAA, fooAB, fooAC]); }); + + it("should handle node core modules", () => { + const file: TestFSWithWatch.File = { + path: "/a/b/app.js", + content: `// @ts-check + +const net = require("net"); +const stream = require("stream");` + }; + const nodeTyping: TestFSWithWatch.File = { + path: `${globalTypingsCacheLocation}/node_modules/node/index.d.ts`, + content: ` +declare module "net" { + export type n = number; +} +declare module "stream" { + export type s = string; +}`, + }; + + const host = createServerHost([file, libFile]); + const installer = new (class extends Installer { + constructor() { + super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("node") }); + } + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) { + executeCommand(this, host, ["node"], [nodeTyping], cb); + } + })(); + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.openClientFile(file.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + + const proj = projectService.inferredProjects[0]; + checkProjectActualFiles(proj, [file.path, libFile.path]); + installer.installAll(/*expectedCount*/ 1); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); + projectService.applyChangesInOpenFiles( + /*openFiles*/ undefined, + arrayIterator([{ + fileName: file.path, + changes: arrayIterator([{ + span: { + start: file.content.indexOf(`"stream"`) + 2, + length: 0 + }, + newText: " " + }]) + }]), + /*closedFiles*/ undefined + ); + // Below timeout Updates the typings to empty array because of "s tream" as unsresolved import + // and schedules the update graph because of this. + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); + + // Here, since typings doesnt contain node typings and resolution fails and + // node typings go out of project, + // but because we handle core node modules when resolving from typings cache + // node typings are included in the project + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); + }); }); describe("unittests:: tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => {