1namespace ts.projectSystem { 2 function createHostModuleResolutionTrace(host: TestServerHost & ModuleResolutionHost) { 3 const resolutionTrace: string[] = []; 4 host.trace = resolutionTrace.push.bind(resolutionTrace); 5 return resolutionTrace; 6 } 7 8 describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem extra resolution pass in server host", () => { 9 it("can load typings that are proper modules", () => { 10 const file1 = { 11 path: "/a/b/app.js", 12 content: `var x = require("lib")` 13 }; 14 const lib = { 15 path: "/a/cache/node_modules/@types/lib/index.d.ts", 16 content: "export let x = 1" 17 }; 18 const host: TestServerHost & ModuleResolutionHost = createServerHost([file1, lib]); 19 const resolutionTrace = createHostModuleResolutionTrace(host); 20 const projectService = createProjectService(host, { typingsInstaller: new TestTypingsInstaller("/a/cache", /*throttleLimit*/5, host) }); 21 22 projectService.setCompilerOptionsForInferredProjects({ traceResolution: true, allowJs: true }); 23 projectService.openClientFile(file1.path); 24 projectService.checkNumberOfProjects({ inferredProjects: 1 }); 25 const proj = projectService.inferredProjects[0]; 26 27 assert.deepEqual(resolutionTrace, [ 28 "======== Resolving module 'lib' from '/a/b/app.js'. ========", 29 "Module resolution kind is not specified, using 'NodeJs'.", 30 "Loading module 'lib' from 'node_modules' folder, target file type 'TypeScript'.", 31 "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.", 32 "Directory '/a/node_modules' does not exist, skipping all lookups in it.", 33 "Directory '/node_modules' does not exist, skipping all lookups in it.", 34 "Loading module 'lib' from 'node_modules' folder, target file type 'JavaScript'.", 35 "Directory '/a/b/node_modules' does not exist, skipping all lookups in it.", 36 "Directory '/a/node_modules' does not exist, skipping all lookups in it.", 37 "Directory '/node_modules' does not exist, skipping all lookups in it.", 38 "======== Module name 'lib' was not resolved. ========", 39 `Auto discovery for typings is enabled in project '${proj.getProjectName()}'. Running extra resolution pass for module 'lib' using cache location '/a/cache'.`, 40 "File '/a/cache/node_modules/lib.d.ts' does not exist.", 41 "File '/a/cache/node_modules/@types/lib/package.json' does not exist.", 42 "File '/a/cache/node_modules/@types/lib.d.ts' does not exist.", 43 "File '/a/cache/node_modules/@types/lib/index.d.ts' exist - use it as a name resolution result.", 44 ]); 45 checkProjectActualFiles(proj, [file1.path, lib.path]); 46 }); 47 }); 48 49 describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem watching @types", () => { 50 it("works correctly when typings are added or removed", () => { 51 const f1 = { 52 path: "/a/b/app.ts", 53 content: "let x = 1;" 54 }; 55 const t1 = { 56 path: "/a/b/node_modules/@types/lib1/index.d.ts", 57 content: "export let a: number" 58 }; 59 const t2 = { 60 path: "/a/b/node_modules/@types/lib2/index.d.ts", 61 content: "export let b: number" 62 }; 63 const tsconfig = { 64 path: "/a/b/tsconfig.json", 65 content: JSON.stringify({ 66 compilerOptions: {}, 67 exclude: ["node_modules"] 68 }) 69 }; 70 const host = createServerHost([f1, t1, tsconfig]); 71 const projectService = createProjectService(host); 72 73 projectService.openClientFile(f1.path); 74 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 75 checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t1.path, tsconfig.path]); 76 77 // delete t1 78 host.deleteFile(t1.path); 79 // run throttled operation 80 host.runQueuedTimeoutCallbacks(); 81 82 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 83 checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, tsconfig.path]); 84 85 // create t2 86 host.writeFile(t2.path, t2.content); 87 // run throttled operation 88 host.runQueuedTimeoutCallbacks(); 89 90 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 91 checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, t2.path, tsconfig.path]); 92 }); 93 }); 94 95 describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem add the missing module file for inferred project", () => { 96 it("should remove the `module not found` error", () => { 97 const moduleFile = { 98 path: "/a/b/moduleFile.ts", 99 content: "export function bar() { };" 100 }; 101 const file1 = { 102 path: "/a/b/file1.ts", 103 content: "import * as T from './moduleFile'; T.bar();" 104 }; 105 const host = createServerHost([file1]); 106 const session = createSession(host); 107 openFilesForSession([file1], session); 108 const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>( 109 server.CommandNames.SemanticDiagnosticsSync, 110 { file: file1.path } 111 ); 112 let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 113 verifyDiagnostics(diags, [ 114 { diagnosticMessage: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, errorTextArguments: ["./moduleFile"] } 115 ]); 116 117 host.writeFile(moduleFile.path, moduleFile.content); 118 host.runQueuedTimeoutCallbacks(); 119 120 // Make a change to trigger the program rebuild 121 const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>( 122 server.CommandNames.Change, 123 { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } 124 ); 125 session.executeCommand(changeRequest); 126 127 // Recheck 128 diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 129 verifyNoDiagnostics(diags); 130 }); 131 132 it("npm install @types works", () => { 133 const folderPath = "/a/b/projects/temp"; 134 const file1: File = { 135 path: `${folderPath}/a.ts`, 136 content: 'import f = require("pad"); f;' 137 }; 138 const host = createServerHost([file1, libFile]); 139 const session = createSession(host, { canUseEvents: true }); 140 const service = session.getProjectService(); 141 session.executeCommandSeq<protocol.OpenRequest>({ 142 command: server.CommandNames.Open, 143 arguments: { 144 file: file1.path, 145 fileContent: file1.content, 146 scriptKindName: "TS", 147 projectRootPath: folderPath 148 } 149 }); 150 checkNumberOfProjects(service, { inferredProjects: 1 }); 151 152 const startOffset = file1.content.indexOf('"') + 1; 153 verifyGetErrRequest({ 154 session, 155 host, 156 expected: [{ 157 file: file1, 158 syntax: [], 159 semantic: [ 160 createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + '"pad"'.length }, Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, ["pad"]) 161 ], 162 suggestion: [] 163 }] 164 }); 165 166 const padIndex: File = { 167 path: `${folderPath}/node_modules/@types/pad/index.d.ts`, 168 content: "export = pad;declare function pad(length: number, text: string, char ?: string): string;" 169 }; 170 host.ensureFileOrFolder(padIndex, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); 171 host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions 172 host.runQueuedTimeoutCallbacks(); // Actual update 173 checkProjectUpdatedInBackgroundEvent(session, [file1.path]); 174 session.clearMessages(); 175 176 host.runQueuedTimeoutCallbacks(); 177 checkErrorMessage(session, "syntaxDiag", { file: file1.path, diagnostics: [] }); 178 session.clearMessages(); 179 180 host.runQueuedImmediateCallbacks(); 181 checkErrorMessage(session, "semanticDiag", { file: file1.path, diagnostics: [] }); 182 }); 183 184 it("suggestion diagnostics", () => { 185 const file: File = { 186 path: "/a.js", 187 content: "function f(p) {}", 188 }; 189 190 const host = createServerHost([file]); 191 const session = createSession(host, { canUseEvents: true }); 192 const service = session.getProjectService(); 193 194 session.executeCommandSeq<protocol.OpenRequest>({ 195 command: server.CommandNames.Open, 196 arguments: { file: file.path, fileContent: file.content }, 197 }); 198 199 checkNumberOfProjects(service, { inferredProjects: 1 }); 200 session.clearMessages(); 201 host.checkTimeoutQueueLength(0); 202 verifyGetErrRequest({ 203 session, 204 host, 205 expected: [{ 206 file, 207 syntax: [], 208 semantic: [], 209 suggestion: [ 210 createDiagnostic({ line: 1, offset: 12 }, { line: 1, offset: 13 }, Diagnostics._0_is_declared_but_its_value_is_never_read, ["p"], "suggestion", /*reportsUnnecessary*/ true), 211 ] 212 }] 213 }); 214 }); 215 216 it("disable suggestion diagnostics", () => { 217 const file: File = { 218 path: "/a.js", 219 content: 'require("b")', 220 }; 221 222 const host = createServerHost([file]); 223 const session = createSession(host, { canUseEvents: true }); 224 const service = session.getProjectService(); 225 226 session.executeCommandSeq<protocol.OpenRequest>({ 227 command: server.CommandNames.Open, 228 arguments: { file: file.path, fileContent: file.content }, 229 }); 230 231 session.executeCommandSeq<protocol.ConfigureRequest>({ 232 command: server.CommandNames.Configure, 233 arguments: { 234 preferences: { disableSuggestions: true } 235 }, 236 }); 237 238 checkNumberOfProjects(service, { inferredProjects: 1 }); 239 session.clearMessages(); 240 host.checkTimeoutQueueLength(0); 241 verifyGetErrRequest({ 242 session, 243 host, 244 expected: [{ 245 file, 246 syntax: [], 247 semantic: [] 248 }] 249 }); 250 }); 251 252 it("suppressed diagnostic events", () => { 253 const file: File = { 254 path: "/a.ts", 255 content: "1 = 2;", 256 }; 257 258 const host = createServerHost([file]); 259 const session = createSession(host, { canUseEvents: true, suppressDiagnosticEvents: true }); 260 const service = session.getProjectService(); 261 262 session.executeCommandSeq<protocol.OpenRequest>({ 263 command: server.CommandNames.Open, 264 arguments: { file: file.path, fileContent: file.content }, 265 }); 266 267 checkNumberOfProjects(service, { inferredProjects: 1 }); 268 269 host.checkTimeoutQueueLength(0); 270 checkNoDiagnosticEvents(session); 271 272 session.clearMessages(); 273 274 let expectedSequenceId = session.getNextSeq(); 275 276 session.executeCommandSeq<protocol.GeterrRequest>({ 277 command: server.CommandNames.Geterr, 278 arguments: { 279 delay: 0, 280 files: [file.path], 281 } 282 }); 283 284 host.checkTimeoutQueueLength(0); 285 checkNoDiagnosticEvents(session); 286 287 checkCompleteEvent(session, 1, expectedSequenceId); 288 289 session.clearMessages(); 290 291 expectedSequenceId = session.getNextSeq(); 292 293 session.executeCommandSeq<protocol.GeterrForProjectRequest>({ 294 command: server.CommandNames.Geterr, 295 arguments: { 296 delay: 0, 297 file: file.path, 298 } 299 }); 300 301 host.checkTimeoutQueueLength(0); 302 checkNoDiagnosticEvents(session); 303 304 checkCompleteEvent(session, 1, expectedSequenceId); 305 306 session.clearMessages(); 307 }); 308 }); 309 310 describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem rename a module file and rename back", () => { 311 it("should restore the states for inferred projects", () => { 312 const moduleFile = { 313 path: "/a/b/moduleFile.ts", 314 content: "export function bar() { };" 315 }; 316 const file1 = { 317 path: "/a/b/file1.ts", 318 content: "import * as T from './moduleFile'; T.bar();" 319 }; 320 const host = createServerHost([moduleFile, file1]); 321 const session = createSession(host); 322 323 openFilesForSession([file1], session); 324 const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>( 325 server.CommandNames.SemanticDiagnosticsSync, 326 { file: file1.path } 327 ); 328 let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 329 verifyNoDiagnostics(diags); 330 331 const moduleFileNewPath = "/a/b/moduleFile1.ts"; 332 host.renameFile(moduleFile.path, moduleFileNewPath); 333 host.runQueuedTimeoutCallbacks(); 334 diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 335 verifyDiagnostics(diags, [ 336 { diagnosticMessage: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, errorTextArguments: ["./moduleFile"] } 337 ]); 338 assert.equal(diags.length, 1); 339 340 host.renameFile(moduleFileNewPath, moduleFile.path); 341 host.runQueuedTimeoutCallbacks(); 342 343 // Make a change to trigger the program rebuild 344 const changeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>( 345 server.CommandNames.Change, 346 { file: file1.path, line: 1, offset: 44, endLine: 1, endOffset: 44, insertString: "\n" } 347 ); 348 session.executeCommand(changeRequest); 349 host.runQueuedTimeoutCallbacks(); 350 351 diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 352 verifyNoDiagnostics(diags); 353 }); 354 355 it("should restore the states for configured projects", () => { 356 const moduleFile = { 357 path: "/a/b/moduleFile.ts", 358 content: "export function bar() { };" 359 }; 360 const file1 = { 361 path: "/a/b/file1.ts", 362 content: "import * as T from './moduleFile'; T.bar();" 363 }; 364 const configFile = { 365 path: "/a/b/tsconfig.json", 366 content: `{}` 367 }; 368 const host = createServerHost([moduleFile, file1, configFile]); 369 const session = createSession(host); 370 371 openFilesForSession([file1], session); 372 const getErrRequest = makeSessionRequest<server.protocol.SemanticDiagnosticsSyncRequestArgs>( 373 server.CommandNames.SemanticDiagnosticsSync, 374 { file: file1.path } 375 ); 376 let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 377 verifyNoDiagnostics(diags); 378 379 const moduleFileNewPath = "/a/b/moduleFile1.ts"; 380 host.renameFile(moduleFile.path, moduleFileNewPath); 381 host.runQueuedTimeoutCallbacks(); 382 diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 383 verifyDiagnostics(diags, [ 384 { diagnosticMessage: Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, errorTextArguments: ["./moduleFile"] } 385 ]); 386 387 host.renameFile(moduleFileNewPath, moduleFile.path); 388 host.runQueuedTimeoutCallbacks(); 389 diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; 390 verifyNoDiagnostics(diags); 391 }); 392 393 it("should property handle missing config files", () => { 394 const f1 = { 395 path: "/a/b/app.ts", 396 content: "let x = 1" 397 }; 398 const config = { 399 path: "/a/b/tsconfig.json", 400 content: "{}" 401 }; 402 const projectName = "project1"; 403 const host = createServerHost([f1]); 404 const projectService = createProjectService(host); 405 projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); 406 407 // should have one external project since config file is missing 408 projectService.checkNumberOfProjects({ externalProjects: 1 }); 409 410 host.writeFile(config.path, config.content); 411 projectService.openExternalProject({ rootFiles: toExternalFiles([f1.path, config.path]), options: {}, projectFileName: projectName }); 412 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 413 }); 414 415 it("types should load from config file path if config exists", () => { 416 const f1 = { 417 path: "/a/b/app.ts", 418 content: "let x = 1" 419 }; 420 const config = { 421 path: "/a/b/tsconfig.json", 422 content: JSON.stringify({ compilerOptions: { types: ["node"], typeRoots: [] } }) 423 }; 424 const node = { 425 path: "/a/b/node_modules/@types/node/index.d.ts", 426 content: "declare var process: any" 427 }; 428 const cwd = { 429 path: "/a/c" 430 }; 431 const host = createServerHost([f1, config, node, cwd], { currentDirectory: cwd.path }); 432 const projectService = createProjectService(host); 433 projectService.openClientFile(f1.path); 434 projectService.checkNumberOfProjects({ configuredProjects: 1 }); 435 checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, node.path, config.path]); 436 }); 437 }); 438 439 describe("unittests:: tsserver:: resolutionCache:: tsserverProjectSystem module resolution caching", () => { 440 const configFile: File = { 441 path: `${tscWatch.projectRoot}/tsconfig.json`, 442 content: JSON.stringify({ compilerOptions: { traceResolution: true } }) 443 }; 444 445 function getModules(module1Path: string, module2Path: string) { 446 const module1: File = { 447 path: module1Path, 448 content: `export function module1() {}` 449 }; 450 const module2: File = { 451 path: module2Path, 452 content: `export function module2() {}` 453 }; 454 return { module1, module2 }; 455 } 456 457 function verifyTrace(resolutionTrace: string[], expected: string[]) { 458 assert.deepEqual(resolutionTrace, expected); 459 resolutionTrace.length = 0; 460 } 461 462 function getExpectedFileDoesNotExistResolutionTrace(host: TestServerHost, expectedTrace: string[], foundModule: boolean, module: File, directory: string, file: string, ignoreIfParentMissing?: boolean) { 463 if (!foundModule) { 464 const path = combinePaths(directory, file); 465 if (!ignoreIfParentMissing || host.directoryExists(getDirectoryPath(path))) { 466 if (module.path === path) { 467 foundModule = true; 468 } 469 else { 470 expectedTrace.push(`File '${path}' does not exist.`); 471 } 472 } 473 } 474 return foundModule; 475 } 476 477 function getExpectedMissedLocationResolutionTrace(host: TestServerHost, expectedTrace: string[], dirPath: string, module: File, moduleName: string, useNodeModules: boolean, cacheLocation?: string) { 478 let foundModule = false; 479 forEachAncestorDirectory(dirPath, dirPath => { 480 if (dirPath === cacheLocation) { 481 return foundModule; 482 } 483 484 const directory = useNodeModules ? combinePaths(dirPath, nodeModules) : dirPath; 485 if (useNodeModules && !foundModule && !host.directoryExists(directory)) { 486 expectedTrace.push(`Directory '${directory}' does not exist, skipping all lookups in it.`); 487 return undefined; 488 } 489 foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/package.json`, /*ignoreIfParentMissing*/ true); 490 foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ts`); 491 foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.tsx`); 492 foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.d.ts`); 493 foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}.ets`); 494 foundModule = getExpectedFileDoesNotExistResolutionTrace(host, expectedTrace, foundModule, module, directory, `${moduleName}/index.ts`, /*ignoreIfParentMissing*/ true); 495 if (useNodeModules && !foundModule) { 496 expectedTrace.push(`Directory '${directory}/@types' does not exist, skipping all lookups in it.`); 497 } 498 return foundModule ? true : undefined; 499 }); 500 } 501 502 function getExpectedResolutionTraceHeader(expectedTrace: string[], file: File, moduleName: string) { 503 expectedTrace.push( 504 `======== Resolving module '${moduleName}' from '${file.path}'. ========`, 505 `Module resolution kind is not specified, using 'NodeJs'.` 506 ); 507 } 508 509 function getExpectedResolutionTraceFooter(expectedTrace: string[], module: File, moduleName: string, addRealPathTrace: boolean, ignoreModuleFileFound?: boolean) { 510 if (!ignoreModuleFileFound) { 511 expectedTrace.push(`File '${module.path}' exist - use it as a name resolution result.`); 512 } 513 if (addRealPathTrace) { 514 expectedTrace.push(`Resolving real path for '${module.path}', result '${module.path}'.`); 515 } 516 expectedTrace.push(`======== Module name '${moduleName}' was successfully resolved to '${module.path}'. ========`); 517 } 518 519 function getExpectedRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) { 520 getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); 521 expectedTrace.push(`Loading module as file / folder, candidate module location '${removeFileExtension(module.path)}', target file type 'TypeScript'.`); 522 getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(normalizePath(combinePaths(getDirectoryPath(file.path), moduleName))), module, moduleName.substring(moduleName.lastIndexOf("/") + 1), /*useNodeModules*/ false); 523 getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false); 524 return expectedTrace; 525 } 526 527 function getExpectedNonRelativeModuleResolutionTrace(host: TestServerHost, file: File, module: File, moduleName: string, expectedTrace: string[] = []) { 528 getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); 529 expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`); 530 getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true); 531 getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ true); 532 return expectedTrace; 533 } 534 535 function getExpectedNonRelativeModuleResolutionFromCacheTrace(host: TestServerHost, file: File, module: File, moduleName: string, cacheLocation: string, expectedTrace: string[] = []) { 536 getExpectedResolutionTraceHeader(expectedTrace, file, moduleName); 537 expectedTrace.push(`Loading module '${moduleName}' from 'node_modules' folder, target file type 'TypeScript'.`); 538 getExpectedMissedLocationResolutionTrace(host, expectedTrace, getDirectoryPath(file.path), module, moduleName, /*useNodeModules*/ true, cacheLocation); 539 expectedTrace.push(`Resolution for module '${moduleName}' was found in cache from location '${cacheLocation}'.`); 540 getExpectedResolutionTraceFooter(expectedTrace, module, moduleName, /*addRealPathTrace*/ false, /*ignoreModuleFileFound*/ true); 541 return expectedTrace; 542 } 543 544 function getExpectedReusingResolutionFromOldProgram(file: File, moduleName: string) { 545 return `Reusing resolution of module '${moduleName}' to file '${file.path}' from old program.`; 546 } 547 548 function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: readonly string[]) { 549 const expectedRecursiveDirectories = new Set([tscWatch.projectRoot, `${tscWatch.projectRoot}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); 550 checkWatchedFiles(host, mapDefined(files, f => { 551 if (f === openFile) { 552 return undefined; 553 } 554 const indexOfNodeModules = f.path.indexOf("/node_modules/"); 555 if (indexOfNodeModules === -1) { 556 return f.path; 557 } 558 expectedRecursiveDirectories.add(f.path.substr(0, indexOfNodeModules + "/node_modules".length)); 559 return undefined; 560 })); 561 checkWatchedDirectories(host, [], /*recursive*/ false); 562 checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.values()), /*recursive*/ true); 563 } 564 565 describe("from files in same folder", () => { 566 function getFiles(fileContent: string) { 567 const file1: File = { 568 path: `${tscWatch.projectRoot}/src/file1.ts`, 569 content: fileContent 570 }; 571 const file2: File = { 572 path: `${tscWatch.projectRoot}/src/file2.ts`, 573 content: fileContent 574 }; 575 return { file1, file2 }; 576 } 577 578 it("relative module name", () => { 579 const module1Name = "./module1"; 580 const module2Name = "../module2"; 581 const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; 582 const { file1, file2 } = getFiles(fileContent); 583 const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/module1.ts`, `${tscWatch.projectRoot}/module2.ts`); 584 const files = [module1, module2, file1, file2, configFile, libFile]; 585 const host = createServerHost(files); 586 const resolutionTrace = createHostModuleResolutionTrace(host); 587 const service = createProjectService(host); 588 service.openClientFile(file1.path); 589 const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name); 590 getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); 591 verifyTrace(resolutionTrace, expectedTrace); 592 verifyWatchesWithConfigFile(host, files, file1); 593 594 host.writeFile(file1.path, file1.content + fileContent); 595 host.writeFile(file2.path, file2.content + fileContent); 596 host.runQueuedTimeoutCallbacks(); 597 verifyTrace(resolutionTrace, [ 598 getExpectedReusingResolutionFromOldProgram(file1, module1Name), 599 getExpectedReusingResolutionFromOldProgram(file1, module2Name) 600 ]); 601 verifyWatchesWithConfigFile(host, files, file1); 602 }); 603 604 it("non relative module name", () => { 605 const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/src`]; 606 const module1Name = "module1"; 607 const module2Name = "module2"; 608 const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; 609 const { file1, file2 } = getFiles(fileContent); 610 const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); 611 const files = [module1, module2, file1, file2, configFile, libFile]; 612 const host = createServerHost(files); 613 const resolutionTrace = createHostModuleResolutionTrace(host); 614 const service = createProjectService(host); 615 service.openClientFile(file1.path); 616 const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); 617 getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); 618 verifyTrace(resolutionTrace, expectedTrace); 619 verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); 620 621 host.writeFile(file1.path, file1.content + fileContent); 622 host.writeFile(file2.path, file2.content + fileContent); 623 host.runQueuedTimeoutCallbacks(); 624 verifyTrace(resolutionTrace, [ 625 getExpectedReusingResolutionFromOldProgram(file1, module1Name), 626 getExpectedReusingResolutionFromOldProgram(file1, module2Name) 627 ]); 628 verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); 629 }); 630 }); 631 632 describe("from files in different folders", () => { 633 function getFiles(fileContent1: string, fileContent2 = fileContent1, fileContent3 = fileContent1, fileContent4 = fileContent1) { 634 const file1: File = { 635 path: `${tscWatch.projectRoot}/product/src/file1.ts`, 636 content: fileContent1 637 }; 638 const file2: File = { 639 path: `${tscWatch.projectRoot}/product/src/feature/file2.ts`, 640 content: fileContent2 641 }; 642 const file3: File = { 643 path: `${tscWatch.projectRoot}/product/test/src/file3.ts`, 644 content: fileContent3 645 }; 646 const file4: File = { 647 path: `${tscWatch.projectRoot}/product/test/file4.ts`, 648 content: fileContent4 649 }; 650 return { file1, file2, file3, file4 }; 651 } 652 653 it("relative module name", () => { 654 const module1Name = "./module1"; 655 const module2Name = "../module2"; 656 const module3Name = "../module1"; 657 const module4Name = "../../module2"; 658 const module5Name = "../../src/module1"; 659 const module6Name = "../src/module1"; 660 const fileContent1 = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; 661 const fileContent2 = `import { module1 } from "${module3Name}";import { module2 } from "${module4Name}";`; 662 const fileContent3 = `import { module1 } from "${module5Name}";import { module2 } from "${module4Name}";`; 663 const fileContent4 = `import { module1 } from "${module6Name}";import { module2 } from "${module2Name}";`; 664 const { file1, file2, file3, file4 } = getFiles(fileContent1, fileContent2, fileContent3, fileContent4); 665 const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/src/module1.ts`, `${tscWatch.projectRoot}/product/module2.ts`); 666 const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; 667 const host = createServerHost(files); 668 const resolutionTrace = createHostModuleResolutionTrace(host); 669 const service = createProjectService(host); 670 service.openClientFile(file1.path); 671 const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, module1, module1Name); 672 getExpectedRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); 673 getExpectedRelativeModuleResolutionTrace(host, file2, module1, module3Name, expectedTrace); 674 getExpectedRelativeModuleResolutionTrace(host, file2, module2, module4Name, expectedTrace); 675 getExpectedRelativeModuleResolutionTrace(host, file4, module1, module6Name, expectedTrace); 676 getExpectedRelativeModuleResolutionTrace(host, file4, module2, module2Name, expectedTrace); 677 getExpectedRelativeModuleResolutionTrace(host, file3, module1, module5Name, expectedTrace); 678 getExpectedRelativeModuleResolutionTrace(host, file3, module2, module4Name, expectedTrace); 679 verifyTrace(resolutionTrace, expectedTrace); 680 verifyWatchesWithConfigFile(host, files, file1); 681 682 host.writeFile(file1.path, file1.content + fileContent1); 683 host.writeFile(file2.path, file2.content + fileContent2); 684 host.writeFile(file3.path, file3.content + fileContent3); 685 host.writeFile(file4.path, file4.content + fileContent4); 686 host.runQueuedTimeoutCallbacks(); 687 688 verifyTrace(resolutionTrace, [ 689 getExpectedReusingResolutionFromOldProgram(file1, module1Name), 690 getExpectedReusingResolutionFromOldProgram(file1, module2Name) 691 ]); 692 verifyWatchesWithConfigFile(host, files, file1); 693 }); 694 695 it("non relative module name", () => { 696 const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/product`]; 697 const module1Name = "module1"; 698 const module2Name = "module2"; 699 const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; 700 const { file1, file2, file3, file4 } = getFiles(fileContent); 701 const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); 702 const files = [module1, module2, file1, file2, file3, file4, configFile, libFile]; 703 const host = createServerHost(files); 704 const resolutionTrace = createHostModuleResolutionTrace(host); 705 const service = createProjectService(host); 706 service.openClientFile(file1.path); 707 const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); 708 getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); 709 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); 710 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); 711 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${tscWatch.projectRoot}/product`, expectedTrace); 712 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${tscWatch.projectRoot}/product`, expectedTrace); 713 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); 714 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); 715 verifyTrace(resolutionTrace, expectedTrace); 716 verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); 717 718 host.writeFile(file1.path, file1.content + fileContent); 719 host.writeFile(file2.path, file2.content + fileContent); 720 host.writeFile(file3.path, file3.content + fileContent); 721 host.writeFile(file4.path, file4.content + fileContent); 722 host.runQueuedTimeoutCallbacks(); 723 724 verifyTrace(resolutionTrace, [ 725 getExpectedReusingResolutionFromOldProgram(file1, module1Name), 726 getExpectedReusingResolutionFromOldProgram(file1, module2Name) 727 ]); 728 verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); 729 }); 730 731 it("non relative module name from inferred project", () => { 732 const module1Name = "module1"; 733 const module2Name = "module2"; 734 const file2Name = "./feature/file2"; 735 const file3Name = "../test/src/file3"; 736 const file4Name = "../test/file4"; 737 const importModuleContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; 738 const { file1, file2, file3, file4 } = getFiles(`import "${file2Name}"; import "${file4Name}"; import "${file3Name}"; ${importModuleContent}`, importModuleContent, importModuleContent, importModuleContent); 739 const { module1, module2 } = getModules(`${tscWatch.projectRoot}/product/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); 740 const files = [module1, module2, file1, file2, file3, file4, libFile]; 741 const host = createServerHost(files); 742 const resolutionTrace = createHostModuleResolutionTrace(host); 743 const service = createProjectService(host); 744 service.setCompilerOptionsForInferredProjects({ traceResolution: true }); 745 service.openClientFile(file1.path); 746 const expectedTrace = getExpectedRelativeModuleResolutionTrace(host, file1, file2, file2Name); 747 getExpectedRelativeModuleResolutionTrace(host, file1, file4, file4Name, expectedTrace); 748 getExpectedRelativeModuleResolutionTrace(host, file1, file3, file3Name, expectedTrace); 749 getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name, expectedTrace); 750 getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); 751 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module1, module1Name, getDirectoryPath(file1.path), expectedTrace); 752 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file2, module2, module2Name, getDirectoryPath(file1.path), expectedTrace); 753 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module1, module1Name, `${tscWatch.projectRoot}/product`, expectedTrace); 754 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file4, module2, module2Name, `${tscWatch.projectRoot}/product`, expectedTrace); 755 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module1, module1Name, getDirectoryPath(file4.path), expectedTrace); 756 getExpectedNonRelativeModuleResolutionFromCacheTrace(host, file3, module2, module2Name, getDirectoryPath(file4.path), expectedTrace); 757 verifyTrace(resolutionTrace, expectedTrace); 758 759 const currentDirectory = getDirectoryPath(file1.path); 760 const watchedFiles = mapDefined(files, f => f === file1 || f.path.indexOf("/node_modules/") !== -1 ? undefined : f.path) 761 .concat(getConfigFilesToWatch(`${tscWatch.projectRoot}/product/src`)); 762 const watchedRecursiveDirectories = getTypeRootsFromLocation(currentDirectory).concat([ 763 `${currentDirectory}/node_modules`, `${currentDirectory}/feature`, `${tscWatch.projectRoot}/product/${nodeModules}`, 764 `${tscWatch.projectRoot}/${nodeModules}`, `${tscWatch.projectRoot}/product/test/${nodeModules}`, 765 `${tscWatch.projectRoot}/product/test/src/${nodeModules}` 766 ]); 767 checkWatches(); 768 769 host.writeFile(file1.path, file1.content + importModuleContent); 770 host.writeFile(file2.path, file2.content + importModuleContent); 771 host.writeFile(file3.path, file3.content + importModuleContent); 772 host.writeFile(file4.path, file4.content + importModuleContent); 773 host.runQueuedTimeoutCallbacks(); 774 775 verifyTrace(resolutionTrace, [ 776 getExpectedReusingResolutionFromOldProgram(file1, file2Name), 777 getExpectedReusingResolutionFromOldProgram(file1, file4Name), 778 getExpectedReusingResolutionFromOldProgram(file1, file3Name), 779 getExpectedReusingResolutionFromOldProgram(file1, module1Name), 780 getExpectedReusingResolutionFromOldProgram(file1, module2Name) 781 ]); 782 checkWatches(); 783 784 function checkWatches() { 785 checkWatchedFiles(host, watchedFiles); 786 checkWatchedDirectories(host, [], /*recursive*/ false); 787 checkWatchedDirectories(host, watchedRecursiveDirectories, /*recursive*/ true); 788 } 789 }); 790 }); 791 792 describe("when watching directories for failed lookup locations in amd resolution", () => { 793 const nodeFile: File = { 794 path: `${tscWatch.projectRoot}/src/typings/node.d.ts`, 795 content: ` 796declare module "fs" { 797 export interface something { 798 } 799}` 800 }; 801 const electronFile: File = { 802 path: `${tscWatch.projectRoot}/src/typings/electron.d.ts`, 803 content: ` 804declare module 'original-fs' { 805 import * as fs from 'fs'; 806 export = fs; 807}` 808 }; 809 const srcFile: File = { 810 path: `${tscWatch.projectRoot}/src/somefolder/srcfile.ts`, 811 content: ` 812import { x } from "somefolder/module1"; 813import { x } from "somefolder/module2"; 814const y = x;` 815 }; 816 const moduleFile: File = { 817 path: `${tscWatch.projectRoot}/src/somefolder/module1.ts`, 818 content: ` 819export const x = 10;` 820 }; 821 const configFile: File = { 822 path: `${tscWatch.projectRoot}/src/tsconfig.json`, 823 content: JSON.stringify({ 824 compilerOptions: { 825 module: "amd", 826 moduleResolution: "classic", 827 target: "es5", 828 outDir: "../out", 829 baseUrl: "./", 830 typeRoots: ["typings"] 831 } 832 }) 833 }; 834 835 function verifyModuleResolution(useNodeFile: boolean) { 836 const files = [...(useNodeFile ? [nodeFile] : []), electronFile, srcFile, moduleFile, configFile, libFile]; 837 const host = createServerHost(files); 838 const service = createProjectService(host); 839 service.openClientFile(srcFile.path, srcFile.content, ScriptKind.TS, tscWatch.projectRoot); 840 checkProjectActualFiles(service.configuredProjects.get(configFile.path)!, files.map(f => f.path)); 841 checkWatchedFilesDetailed(host, mapDefined(files, f => f === srcFile ? undefined : f.path), 1); 842 if (useNodeFile) { 843 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); // since fs resolves to ambient module, shouldnt watch failed lookup 844 } 845 else { 846 checkWatchedDirectoriesDetailed(host, [`${tscWatch.projectRoot}`, `${tscWatch.projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs 847 } 848 const expectedWatchedDirectories = new Map<string, number>(); 849 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src`, 1); // Wild card 850 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2 851 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2 852 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/somefolder`, 1); // failed lookup for somefolder/module2 853 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/node_modules`, 1); // failed lookup for with node_modules/@types/fs 854 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/typings`, useNodeFile ? 1 : 2); // typeroot directory + failed lookup if not using node file 855 checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ true); 856 } 857 858 it("when resolves to ambient module", () => { 859 verifyModuleResolution(/*useNodeFile*/ true); 860 }); 861 862 it("when resolution fails", () => { 863 verifyModuleResolution(/*useNodeFile*/ false); 864 }); 865 }); 866 867 describe("ignores files/folder changes in node_modules that start with '.'", () => { 868 const npmCacheFile: File = { 869 path: `${tscWatch.projectRoot}/node_modules/.cache/babel-loader/89c02171edab901b9926470ba6d5677e.ts`, 870 content: JSON.stringify({ something: 10 }) 871 }; 872 const file1: File = { 873 path: `${tscWatch.projectRoot}/test.ts`, 874 content: `import { x } from "somemodule";` 875 }; 876 const file2: File = { 877 path: `${tscWatch.projectRoot}/node_modules/somemodule/index.d.ts`, 878 content: `export const x = 10;` 879 }; 880 it("when watching node_modules in inferred project for failed lookup/closed script infos", () => { 881 const files = [libFile, file1, file2]; 882 const host = createServerHost(files); 883 const service = createProjectService(host); 884 service.openClientFile(file1.path); 885 checkNumberOfProjects(service, { inferredProjects: 1 }); 886 const project = service.inferredProjects[0]; 887 checkProjectActualFiles(project, files.map(f => f.path)); 888 host.checkTimeoutQueueLength(0); 889 890 host.ensureFileOrFolder(npmCacheFile); 891 host.checkTimeoutQueueLength(0); 892 }); 893 it("when watching node_modules as part of wild card directories in config project", () => { 894 const config: File = { 895 path: `${tscWatch.projectRoot}/tsconfig.json`, 896 content: "{}" 897 }; 898 const files = [libFile, file1, file2, config]; 899 const host = createServerHost(files); 900 const service = createProjectService(host); 901 service.openClientFile(file1.path); 902 checkNumberOfProjects(service, { configuredProjects: 1 }); 903 const project = Debug.checkDefined(service.configuredProjects.get(config.path)); 904 checkProjectActualFiles(project, files.map(f => f.path)); 905 host.checkTimeoutQueueLength(0); 906 907 host.ensureFileOrFolder(npmCacheFile); 908 host.checkTimeoutQueueLength(0); 909 }); 910 }); 911 912 describe("avoid unnecessary invalidation", () => { 913 it("unnecessary lookup invalidation on save", () => { 914 const expectedNonRelativeDirectories = [`${tscWatch.projectRoot}/node_modules`, `${tscWatch.projectRoot}/src`]; 915 const module1Name = "module1"; 916 const module2Name = "module2"; 917 const fileContent = `import { module1 } from "${module1Name}";import { module2 } from "${module2Name}";`; 918 const file1: File = { 919 path: `${tscWatch.projectRoot}/src/file1.ts`, 920 content: fileContent 921 }; 922 const { module1, module2 } = getModules(`${tscWatch.projectRoot}/src/node_modules/module1/index.ts`, `${tscWatch.projectRoot}/node_modules/module2/index.ts`); 923 const files = [module1, module2, file1, configFile, libFile]; 924 const host = createServerHost(files); 925 const resolutionTrace = createHostModuleResolutionTrace(host); 926 const service = createProjectService(host); 927 service.openClientFile(file1.path); 928 const expectedTrace = getExpectedNonRelativeModuleResolutionTrace(host, file1, module1, module1Name); 929 getExpectedNonRelativeModuleResolutionTrace(host, file1, module2, module2Name, expectedTrace); 930 verifyTrace(resolutionTrace, expectedTrace); 931 verifyWatchesWithConfigFile(host, files, file1, expectedNonRelativeDirectories); 932 933 // invoke callback to simulate saving 934 host.modifyFile(file1.path, file1.content, { invokeFileDeleteCreateAsPartInsteadOfChange: true }); 935 host.checkTimeoutQueueLengthAndRun(0); 936 }); 937 }); 938 }); 939} 940