From 6ed8aae68323ccf33eb7ae65a72e0e7bc8bfa4d7 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jun 2019 15:00:28 -0700 Subject: [PATCH 1/2] Test when node typings dont get added on subsequent update graph because of invalid typing Test for #29865 --- .../unittests/tsserver/typingsInstaller.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index 76df9934682fd..a2111d131e939 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -1773,6 +1773,68 @@ 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 + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(proj, [file.path, libFile.path]); + }); }); describe("unittests:: tsserver:: typingsInstaller:: tsserver:: with inferred Project", () => { From 2fd80b3142672364e16d4127369642564bac30db Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Jun 2019 16:21:39 -0700 Subject: [PATCH 2/2] When resolving from typings cache, handle node code modules Fixes #29865 --- src/compiler/resolutionCache.ts | 8 +++++++- src/jsTyping/jsTyping.ts | 6 +++++- src/server/project.ts | 3 +++ src/testRunner/unittests/tsserver/typingsInstaller.ts | 6 ++++-- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index fb04d7b7a187e..9755022e3137c 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; @@ -235,7 +236,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 379ca6491a61c..c2b2ad3f5b39c 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 e10a0988f7185..a1a43a430f104 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 a2111d131e939..bcce68fe0de3f 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -1831,9 +1831,11 @@ declare module "stream" { 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 + // 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]); + checkProjectActualFiles(proj, [file.path, libFile.path, nodeTyping.path]); }); });