1namespace ts.projectSystem { 2 import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; 3 describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watchDirectories implementation", () => { 4 function verifyCompletionListWithNewFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) { 5 const projectFolder = "/a/username/project"; 6 const projectSrcFolder = `${projectFolder}/src`; 7 const configFile: File = { 8 path: `${projectFolder}/tsconfig.json`, 9 content: JSON.stringify({ 10 watchOptions: { 11 synchronousWatchDirectory: true 12 } 13 }) 14 }; 15 const index: File = { 16 path: `${projectSrcFolder}/index.ts`, 17 content: `import {} from "./"` 18 }; 19 const file1: File = { 20 path: `${projectSrcFolder}/file1.ts`, 21 content: "" 22 }; 23 24 const files = [index, file1, configFile, libFile]; 25 const fileNames = files.map(file => file.path); 26 // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder 27 const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); 28 const expectedWatchedDirectories = new Map<string, number>(); 29 const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? 30 expectedWatchedDirectories : 31 tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? 32 expectedWatchedFiles : 33 new Map(); 34 // For failed resolution lookup and tsconfig files => cached so only watched only once 35 mapOfDirectories.set(projectFolder, 1); 36 // Through above recursive watches 37 mapOfDirectories.set(projectSrcFolder, 1); 38 // node_modules/@types folder 39 mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); 40 const expectedCompletions = ["file1"]; 41 const completionPosition = index.content.lastIndexOf('"'); 42 const environmentVariables = new Map<string, string>(); 43 environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); 44 const host = createServerHost(files, { environmentVariables }); 45 const projectService = createProjectService(host); 46 projectService.openClientFile(index.path); 47 48 const project = Debug.checkDefined(projectService.configuredProjects.get(configFile.path)); 49 verifyProjectAndCompletions(); 50 51 // Add file2 52 const file2: File = { 53 path: `${projectSrcFolder}/file2.ts`, 54 content: "" 55 }; 56 fileNames.push(file2.path); 57 expectedWatchedFiles.set(file2.path, 1); 58 expectedCompletions.push("file2"); 59 host.writeFile(file2.path, file2.content); 60 host.runQueuedTimeoutCallbacks(); 61 assert.equal(projectService.configuredProjects.get(configFile.path), project); 62 verifyProjectAndCompletions(); 63 64 function verifyProjectAndCompletions() { 65 const completions = project.getLanguageService().getCompletionsAtPosition(index.path, completionPosition, { includeExternalModuleExports: false, includeInsertTextCompletions: false })!; 66 checkArray("Completion Entries", completions.entries.map(e => e.name), expectedCompletions); 67 68 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 69 70 checkWatchedFilesDetailed(host, expectedWatchedFiles); 71 checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); 72 checkProjectActualFiles(project, fileNames); 73 } 74 } 75 76 it("uses watchFile when file is added to subfolder, completion list has new file", () => { 77 verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.WatchFile); 78 }); 79 80 it("uses non recursive watchDirectory when file is added to subfolder, completion list has new file", () => { 81 verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory); 82 }); 83 84 it("uses dynamic polling when file is added to subfolder, completion list has new file", () => { 85 verifyCompletionListWithNewFileInSubFolder(Tsc_WatchDirectory.DynamicPolling); 86 }); 87 }); 88 89 describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem Watched recursive directories with windows style file system", () => { 90 function verifyWatchedDirectories(rootedPath: string, useProjectAtRoot: boolean) { 91 const root = useProjectAtRoot ? rootedPath : `${rootedPath}myfolder/allproject/`; 92 const configFile: File = { 93 path: root + "project/tsconfig.json", 94 content: "{}" 95 }; 96 const file1: File = { 97 path: root + "project/file1.ts", 98 content: "let x = 10;" 99 }; 100 const file2: File = { 101 path: root + "project/file2.ts", 102 content: "let y = 10;" 103 }; 104 const files = [configFile, file1, file2, libFile]; 105 const host = createServerHost(files, { windowsStyleRoot: "c:/" }); 106 const projectService = createProjectService(host); 107 projectService.openClientFile(file1.path); 108 const project = projectService.configuredProjects.get(configFile.path)!; 109 assert.isDefined(project); 110 const winsowsStyleLibFilePath = "c:/" + libFile.path.substring(1); 111 checkProjectActualFiles(project, files.map(f => f === libFile ? winsowsStyleLibFilePath : f.path)); 112 checkWatchedFiles(host, mapDefined(files, f => f === libFile ? winsowsStyleLibFilePath : f === file1 ? undefined : f.path)); 113 checkWatchedDirectories(host, [], /*recursive*/ false); 114 checkWatchedDirectories(host, [ 115 root + "project", 116 root + "project/node_modules/@types" 117 ].concat(useProjectAtRoot ? [] : [root + nodeModulesAtTypes]), /*recursive*/ true); 118 } 119 120 function verifyRootedDirectoryWatch(rootedPath: string) { 121 it("When project is in rootFolder of style c:/", () => { 122 verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ true); 123 }); 124 125 it("When files at some folder other than root", () => { 126 verifyWatchedDirectories(rootedPath, /*useProjectAtRoot*/ false); 127 }); 128 } 129 130 describe("for rootFolder of style c:/", () => { 131 verifyRootedDirectoryWatch("c:/"); 132 }); 133 134 describe("for rootFolder of style c:/users/username", () => { 135 verifyRootedDirectoryWatch("c:/users/username/"); 136 }); 137 }); 138 139 it(`unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem recursive watch directory implementation does not watch files/directories in node_modules starting with "."`, () => { 140 const projectFolder = "/a/username/project"; 141 const projectSrcFolder = `${projectFolder}/src`; 142 const configFile: File = { 143 path: `${projectFolder}/tsconfig.json`, 144 content: "{}" 145 }; 146 const index: File = { 147 path: `${projectSrcFolder}/index.ts`, 148 content: `import {} from "file"` 149 }; 150 const file1: File = { 151 path: `${projectSrcFolder}/file1.ts`, 152 content: "" 153 }; 154 const nodeModulesExistingUnusedFile: File = { 155 path: `${projectFolder}/node_modules/someFile.d.ts`, 156 content: "" 157 }; 158 159 const fileNames = [index, file1, configFile, libFile].map(file => file.path); 160 // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder 161 const expectedWatchedFiles = arrayToMap(fileNames.slice(1), identity, () => 1); 162 const expectedWatchedDirectories = arrayToMap([projectFolder, projectSrcFolder, `${projectFolder}/${nodeModules}`, `${projectFolder}/${nodeModulesAtTypes}`], identity, () => 1); 163 164 const environmentVariables = new Map<string, string>(); 165 environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); 166 const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables }); 167 const projectService = createProjectService(host); 168 projectService.openClientFile(index.path); 169 170 const project = Debug.checkDefined(projectService.configuredProjects.get(configFile.path)); 171 verifyProject(); 172 173 const nodeModulesIgnoredFileFromIgnoreDirectory: File = { 174 path: `${projectFolder}/node_modules/.cache/someFile.d.ts`, 175 content: "" 176 }; 177 178 const nodeModulesIgnoredFile: File = { 179 path: `${projectFolder}/node_modules/.cacheFile.ts`, 180 content: "" 181 }; 182 183 const gitIgnoredFileFromIgnoreDirectory: File = { 184 path: `${projectFolder}/.git/someFile.d.ts`, 185 content: "" 186 }; 187 188 const gitIgnoredFile: File = { 189 path: `${projectFolder}/.gitCache.d.ts`, 190 content: "" 191 }; 192 const emacsIgnoredFileFromIgnoreDirectory: File = { 193 path: `${projectFolder}/src/.#field.ts`, 194 content: "" 195 }; 196 197 [ 198 nodeModulesIgnoredFileFromIgnoreDirectory, 199 nodeModulesIgnoredFile, 200 gitIgnoredFileFromIgnoreDirectory, 201 gitIgnoredFile, 202 emacsIgnoredFileFromIgnoreDirectory 203 ].forEach(ignoredEntity => { 204 host.ensureFileOrFolder(ignoredEntity); 205 host.checkTimeoutQueueLength(0); 206 verifyProject(); 207 }); 208 209 function verifyProject() { 210 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 211 checkWatchedFilesDetailed(host, expectedWatchedFiles); 212 checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false); 213 checkProjectActualFiles(project, fileNames); 214 } 215 }); 216 217 describe("unittests:: tsserver:: watchEnvironment:: tsserverProjectSystem watching files with network style paths", () => { 218 function verifyFilePathStyle(path: string) { 219 const windowsStyleRoot = path.substr(0, getRootLength(path)); 220 const host = createServerHost( 221 [libFile, { path, content: "const x = 10" }], 222 { windowsStyleRoot } 223 ); 224 const service = createProjectService(host); 225 service.openClientFile(path); 226 checkNumberOfProjects(service, { inferredProjects: 1 }); 227 const libPath = `${windowsStyleRoot}${libFile.path.substring(1)}`; 228 checkProjectActualFiles(service.inferredProjects[0], [path, libPath]); 229 checkWatchedFiles(host, [libPath, `${getDirectoryPath(path)}/tsconfig.json`, `${getDirectoryPath(path)}/jsconfig.json`]); 230 } 231 232 it("for file of style c:/myprojects/project/x.js", () => { 233 verifyFilePathStyle("c:/myprojects/project/x.js"); 234 }); 235 236 it("for file of style //vda1cs4850/myprojects/project/x.js", () => { 237 verifyFilePathStyle("//vda1cs4850/myprojects/project/x.js"); 238 }); 239 240 it("for file of style //vda1cs4850/c$/myprojects/project/x.js", () => { 241 verifyFilePathStyle("//vda1cs4850/c$/myprojects/project/x.js"); 242 }); 243 244 it("for file of style c:/users/username/myprojects/project/x.js", () => { 245 verifyFilePathStyle("c:/users/username/myprojects/project/x.js"); 246 }); 247 248 it("for file of style //vda1cs4850/c$/users/username/myprojects/project/x.js", () => { 249 verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js"); 250 }); 251 }); 252 253 describe("unittests:: tsserver:: watchEnvironment:: handles watch compiler options", () => { 254 it("with watchFile option as host configuration", () => { 255 const configFile: File = { 256 path: "/a/b/tsconfig.json", 257 content: "{}" 258 }; 259 const files = [libFile, commonFile2, configFile]; 260 const host = createServerHost(files.concat(commonFile1)); 261 const session = createSession(host); 262 session.executeCommandSeq<protocol.ConfigureRequest>({ 263 command: protocol.CommandTypes.Configure, 264 arguments: { 265 watchOptions: { 266 watchFile: protocol.WatchFileKind.UseFsEvents 267 } 268 } 269 }); 270 const service = session.getProjectService(); 271 openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session); 272 checkProjectActualFiles( 273 service.configuredProjects.get(configFile.path)!, 274 files.map(f => f.path).concat(commonFile1.path) 275 ); 276 277 // Instead of polling watch (= watchedFiles), uses fsWatch 278 checkWatchedFiles(host, emptyArray); 279 checkWatchedDirectoriesDetailed( 280 host, 281 files.map(f => f.path.toLowerCase()), 282 1, 283 /*recursive*/ false, 284 arrayToMap( 285 files, 286 f => f.path.toLowerCase(), 287 f => [{ 288 directoryName: f.path, 289 fallbackPollingInterval: f === configFile ? PollingInterval.High : PollingInterval.Medium, 290 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 291 }] 292 ) 293 ); 294 checkWatchedDirectoriesDetailed( 295 host, 296 ["/a/b", "/a/b/node_modules/@types"], 297 1, 298 /*recursive*/ true, 299 arrayToMap( 300 ["/a/b", "/a/b/node_modules/@types"], 301 identity, 302 directoryName => [{ 303 directoryName, 304 fallbackPollingInterval: PollingInterval.Medium, 305 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 306 }] 307 ) 308 ); 309 }); 310 311 it("with watchDirectory option as host configuration", () => { 312 const configFile: File = { 313 path: "/a/b/tsconfig.json", 314 content: "{}" 315 }; 316 const files = [libFile, commonFile2, configFile]; 317 const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true }); 318 const session = createSession(host); 319 session.executeCommandSeq<protocol.ConfigureRequest>({ 320 command: protocol.CommandTypes.Configure, 321 arguments: { 322 watchOptions: { 323 watchDirectory: protocol.WatchDirectoryKind.UseFsEvents 324 } 325 } 326 }); 327 const service = session.getProjectService(); 328 openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session); 329 checkProjectActualFiles( 330 service.configuredProjects.get(configFile.path)!, 331 files.map(f => f.path).concat(commonFile1.path) 332 ); 333 334 checkWatchedFilesDetailed( 335 host, 336 files.map(f => f.path.toLowerCase()), 337 1, 338 arrayToMap( 339 files, 340 f => f.path.toLowerCase(), 341 f => [{ 342 fileName: f.path, 343 pollingInterval: PollingInterval.Low 344 }] 345 ) 346 ); 347 checkWatchedDirectoriesDetailed( 348 host, 349 ["/a/b", "/a/b/node_modules/@types"], 350 1, 351 /*recursive*/ false, 352 arrayToMap( 353 ["/a/b", "/a/b/node_modules/@types"], 354 identity, 355 directoryName => [{ 356 directoryName, 357 fallbackPollingInterval: PollingInterval.Medium, 358 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 359 }] 360 ) 361 ); 362 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 363 }); 364 365 it("with fallbackPolling option as host configuration", () => { 366 const configFile: File = { 367 path: "/a/b/tsconfig.json", 368 content: "{}" 369 }; 370 const files = [libFile, commonFile2, configFile]; 371 const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); 372 const session = createSession(host); 373 session.executeCommandSeq<protocol.ConfigureRequest>({ 374 command: protocol.CommandTypes.Configure, 375 arguments: { 376 watchOptions: { 377 fallbackPolling: protocol.PollingWatchKind.PriorityInterval 378 } 379 } 380 }); 381 const service = session.getProjectService(); 382 openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session); 383 checkProjectActualFiles( 384 service.configuredProjects.get(configFile.path)!, 385 files.map(f => f.path).concat(commonFile1.path) 386 ); 387 388 const filePaths = files.map(f => f.path); 389 const allFilePaths = filePaths.concat(["/a/b", "/a/b/node_modules/@types"]); 390 checkWatchedFilesDetailed( 391 host, 392 allFilePaths.map(toLowerCase), 393 1, 394 arrayToMap( 395 allFilePaths, 396 toLowerCase, 397 fileName => [{ 398 fileName, 399 pollingInterval: contains(filePaths, fileName) ? PollingInterval.Low : PollingInterval.Medium 400 }] 401 ) 402 ); 403 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 404 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 405 }); 406 407 it("with watchFile option in configFile", () => { 408 const configFile: File = { 409 path: "/a/b/tsconfig.json", 410 content: JSON.stringify({ 411 watchOptions: { 412 watchFile: "UseFsEvents" 413 } 414 }) 415 }; 416 const files = [libFile, commonFile2, configFile]; 417 const host = createServerHost(files.concat(commonFile1)); 418 const session = createSession(host); 419 const service = session.getProjectService(); 420 openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session); 421 checkProjectActualFiles( 422 service.configuredProjects.get(configFile.path)!, 423 files.map(f => f.path).concat(commonFile1.path) 424 ); 425 426 // The closed script infos are watched using host settings 427 checkWatchedFilesDetailed( 428 host, 429 [libFile, commonFile2].map(f => f.path.toLowerCase()), 430 1, 431 arrayToMap( 432 [libFile, commonFile2], 433 f => f.path.toLowerCase(), 434 f => [{ 435 fileName: f.path, 436 pollingInterval: PollingInterval.Low 437 }] 438 ) 439 ); 440 // Config file with the setting with fsWatch 441 checkWatchedDirectoriesDetailed( 442 host, 443 [configFile.path.toLowerCase()], 444 1, 445 /*recursive*/ false, 446 arrayToMap( 447 [configFile.path], 448 toLowerCase, 449 directoryName => [{ 450 directoryName, 451 fallbackPollingInterval: PollingInterval.High, 452 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 453 }] 454 ) 455 ); 456 checkWatchedDirectoriesDetailed( 457 host, 458 ["/a/b", "/a/b/node_modules/@types"], 459 1, 460 /*recursive*/ true, 461 arrayToMap( 462 ["/a/b", "/a/b/node_modules/@types"], 463 identity, 464 directoryName => [{ 465 directoryName, 466 fallbackPollingInterval: PollingInterval.Medium, 467 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 468 }] 469 ) 470 ); 471 }); 472 473 it("with watchDirectory option in configFile", () => { 474 const configFile: File = { 475 path: "/a/b/tsconfig.json", 476 content: JSON.stringify({ 477 watchOptions: { 478 watchDirectory: "UseFsEvents" 479 } 480 }) 481 }; 482 const files = [libFile, commonFile2, configFile]; 483 const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true }); 484 const session = createSession(host); 485 const service = session.getProjectService(); 486 openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session); 487 checkProjectActualFiles( 488 service.configuredProjects.get(configFile.path)!, 489 files.map(f => f.path).concat(commonFile1.path) 490 ); 491 492 checkWatchedFilesDetailed( 493 host, 494 files.map(f => f.path.toLowerCase()), 495 1, 496 arrayToMap( 497 files, 498 f => f.path.toLowerCase(), 499 f => [{ 500 fileName: f.path, 501 pollingInterval: PollingInterval.Low 502 }] 503 ) 504 ); 505 checkWatchedDirectoriesDetailed( 506 host, 507 ["/a/b", "/a/b/node_modules/@types"], 508 1, 509 /*recursive*/ false, 510 arrayToMap( 511 ["/a/b", "/a/b/node_modules/@types"], 512 identity, 513 directoryName => [{ 514 directoryName, 515 fallbackPollingInterval: PollingInterval.Medium, 516 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 517 }] 518 ) 519 ); 520 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 521 }); 522 523 it("with fallbackPolling option in configFile", () => { 524 const configFile: File = { 525 path: "/a/b/tsconfig.json", 526 content: JSON.stringify({ 527 watchOptions: { 528 fallbackPolling: "PriorityInterval" 529 } 530 }) 531 }; 532 const files = [libFile, commonFile2, configFile]; 533 const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); 534 const session = createSession(host); 535 session.executeCommandSeq<protocol.ConfigureRequest>({ 536 command: protocol.CommandTypes.Configure, 537 arguments: { 538 watchOptions: { 539 fallbackPolling: protocol.PollingWatchKind.PriorityInterval 540 } 541 } 542 }); 543 const service = session.getProjectService(); 544 openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session); 545 checkProjectActualFiles( 546 service.configuredProjects.get(configFile.path)!, 547 files.map(f => f.path).concat(commonFile1.path) 548 ); 549 550 const filePaths = files.map(f => f.path); 551 const allFilePaths = filePaths.concat(["/a/b", "/a/b/node_modules/@types"]); 552 checkWatchedFilesDetailed( 553 host, 554 allFilePaths.map(toLowerCase), 555 1, 556 arrayToMap( 557 allFilePaths, 558 toLowerCase, 559 fileName => [{ 560 fileName, 561 pollingInterval: contains(filePaths, fileName) ? PollingInterval.Low : PollingInterval.Medium 562 }] 563 ) 564 ); 565 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 566 checkWatchedDirectories(host, emptyArray, /*recursive*/ true); 567 }); 568 569 describe("excludeDirectories", () => { 570 function setupFiles() { 571 const main: File = { 572 path: `${tscWatch.projectRoot}/src/main.ts`, 573 content: `import { foo } from "bar"; foo();` 574 }; 575 const bar: File = { 576 path: `${tscWatch.projectRoot}/node_modules/bar/index.d.ts`, 577 content: `export { foo } from "./foo";` 578 }; 579 const foo: File = { 580 path: `${tscWatch.projectRoot}/node_modules/bar/foo.d.ts`, 581 content: `export function foo(): string;` 582 }; 583 return { main, bar, foo }; 584 } 585 586 function setupConfigureHost(service: TestProjectService, configureHost: boolean | undefined) { 587 if (configureHost) { 588 service.setHostConfiguration({ 589 watchOptions: { excludeDirectories: ["node_modules"] } 590 }); 591 } 592 } 593 function setup(configureHost?: boolean) { 594 const configFile: File = { 595 path: `${tscWatch.projectRoot}/tsconfig.json`, 596 content: JSON.stringify({ include: ["src"], watchOptions: { excludeDirectories: ["node_modules"] } }) 597 }; 598 const { main, bar, foo } = setupFiles(); 599 const files = [libFile, main, bar, foo, configFile]; 600 const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); 601 const service = createProjectService(host); 602 setupConfigureHost(service, configureHost); 603 service.openClientFile(main.path); 604 return { host, configFile }; 605 } 606 607 it("with excludeDirectories option in configFile", () => { 608 const { host, configFile } = setup(); 609 checkWatchedFilesDetailed(host, [configFile.path, libFile.path], 1); 610 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 611 checkWatchedDirectoriesDetailed( 612 host, 613 arrayToMap( 614 [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`], 615 identity, 616 f => f === `${tscWatch.projectRoot}/node_modules` ? 1 : 2, 617 ), 618 /*recursive*/ true, 619 ); 620 }); 621 622 it("with excludeDirectories option in configuration", () => { 623 const { host, configFile } = setup(/*configureHost*/ true); 624 checkWatchedFilesDetailed(host, [configFile.path, libFile.path], 1); 625 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 626 checkWatchedDirectoriesDetailed( 627 host, 628 [`${tscWatch.projectRoot}/src`], 629 2, 630 /*recursive*/ true, 631 ); 632 }); 633 634 function setupExternalProject(configureHost?: boolean) { 635 const { main, bar, foo } = setupFiles(); 636 const files = [libFile, main, bar, foo]; 637 const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); 638 const service = createProjectService(host); 639 setupConfigureHost(service, configureHost); 640 service.openExternalProject(<protocol.ExternalProject>{ 641 projectFileName: `${tscWatch.projectRoot}/project.csproj`, 642 rootFiles: toExternalFiles([main.path, bar.path, foo.path]), 643 options: { excludeDirectories: ["node_modules"] } 644 }); 645 service.openClientFile(main.path); 646 return host; 647 } 648 649 it("external project watch options", () => { 650 const host = setupExternalProject(); 651 checkWatchedFilesDetailed(host, [libFile.path], 1); 652 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 653 checkWatchedDirectoriesDetailed( 654 host, 655 [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`], 656 1, 657 /*recursive*/ true, 658 ); 659 }); 660 661 it("external project watch options in host configuration", () => { 662 const host = setupExternalProject(/*configureHost*/ true); 663 checkWatchedFilesDetailed(host, [libFile.path], 1); 664 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 665 checkWatchedDirectoriesDetailed( 666 host, 667 [`${tscWatch.projectRoot}/src`], 668 1, 669 /*recursive*/ true, 670 ); 671 }); 672 673 it("external project watch options errors", () => { 674 const { main, bar, foo } = setupFiles(); 675 const files = [libFile, main, bar, foo]; 676 const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); 677 const service = createProjectService(host); 678 service.openExternalProject(<protocol.ExternalProject>{ 679 projectFileName: `${tscWatch.projectRoot}/project.csproj`, 680 rootFiles: toExternalFiles([main.path, bar.path, foo.path]), 681 options: { excludeDirectories: ["**/../*"] } 682 }); 683 service.openClientFile(main.path); 684 const project = service.externalProjects[0]; 685 assert.deepEqual(project.getAllProjectErrors(), [ 686 { 687 messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, 688 category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, 689 code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, 690 file: undefined, 691 start: undefined, 692 length: undefined, 693 reportsDeprecated: undefined, 694 reportsUnnecessary: undefined, 695 } 696 ]); 697 }); 698 699 function setupInferredProject(configureHost?: boolean) { 700 const { main, bar, foo } = setupFiles(); 701 const files = [libFile, main, bar, foo]; 702 const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); 703 const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); 704 setupConfigureHost(service, configureHost); 705 service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["node_modules"] }, tscWatch.projectRoot); 706 service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot); 707 return host; 708 } 709 710 it("inferred project watch options", () => { 711 const host = setupInferredProject(); 712 checkWatchedFilesDetailed( 713 host, 714 [libFile.path, `${tscWatch.projectRoot}/tsconfig.json`, `${tscWatch.projectRoot}/jsconfig.json`, `${tscWatch.projectRoot}/src/tsconfig.json`, `${tscWatch.projectRoot}/src/jsconfig.json`], 715 1 716 ); 717 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 718 checkWatchedDirectoriesDetailed( 719 host, 720 [`${tscWatch.projectRoot}/src`, `${tscWatch.projectRoot}/node_modules`], 721 1, 722 /*recursive*/ true, 723 ); 724 }); 725 726 it("inferred project watch options in host configuration", () => { 727 const host = setupInferredProject(/*configureHost*/ true); 728 checkWatchedFilesDetailed( 729 host, 730 [libFile.path, `${tscWatch.projectRoot}/tsconfig.json`, `${tscWatch.projectRoot}/jsconfig.json`, `${tscWatch.projectRoot}/src/tsconfig.json`, `${tscWatch.projectRoot}/src/jsconfig.json`], 731 1 732 ); 733 checkWatchedDirectories(host, emptyArray, /*recursive*/ false); 734 checkWatchedDirectoriesDetailed( 735 host, 736 [`${tscWatch.projectRoot}/src`], 737 1, 738 /*recursive*/ true, 739 ); 740 }); 741 742 it("inferred project watch options errors", () => { 743 const { main, bar, foo } = setupFiles(); 744 const files = [libFile, main, bar, foo]; 745 const host = createServerHost(files, { currentDirectory: tscWatch.projectRoot }); 746 const service = createProjectService(host, { useInferredProjectPerProjectRoot: true }); 747 service.setCompilerOptionsForInferredProjects({ excludeDirectories: ["**/../*"] }, tscWatch.projectRoot); 748 service.openClientFile(main.path, main.content, ScriptKind.TS, tscWatch.projectRoot); 749 const project = service.inferredProjects[0]; 750 assert.deepEqual(project.getAllProjectErrors(), [ 751 { 752 messageText: `File specification cannot contain a parent directory ('..') that appears after a recursive directory wildcard ('**'): '**/../*'.`, 753 category: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.category, 754 code: Diagnostics.File_specification_cannot_contain_a_parent_directory_that_appears_after_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0.code, 755 file: undefined, 756 start: undefined, 757 length: undefined, 758 reportsDeprecated: undefined, 759 reportsUnnecessary: undefined, 760 } 761 ]); 762 }); 763 }); 764 }); 765 766 describe("unittests:: tsserver:: watchEnvironment:: file names on case insensitive file system", () => { 767 function verifyFileNames(projectRoot: string, projectRootPath: string) { 768 const keyMapper = (str: string) => str.replace(projectRoot, projectRootPath); 769 const file: File = { 770 path: `${projectRoot}/foo.ts`, 771 content: `import { foo } from "bar"` 772 }; 773 const host = createServerHost([file, libFile]); 774 const service = createProjectService(host); 775 service.openClientFile(file.path, /*fileContent*/ undefined, /*scriptKind*/ undefined, projectRoot); 776 const expectedWatchFiles = [libFile.path, `${projectRoot}/tsconfig.json`, `${projectRoot}/jsconfig.json`]; 777 checkWatchedFilesDetailed( 778 host, 779 expectedWatchFiles.map(keyMapper), 780 1, 781 arrayToMap( 782 expectedWatchFiles, 783 keyMapper, 784 fileName => [{ 785 fileName, 786 pollingInterval: PollingInterval.Low 787 }] 788 ) 789 ); 790 checkWatchedDirectories(host, [], /*recursive*/ false); 791 const expectedWatchedDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/node_modules/@types`]; 792 checkWatchedDirectoriesDetailed( 793 host, 794 expectedWatchedDirectories.map(keyMapper), 795 1, 796 /*recursive*/ true, 797 arrayToMap( 798 expectedWatchedDirectories, 799 keyMapper, 800 directoryName => [{ 801 directoryName, 802 fallbackPollingInterval: PollingInterval.Medium, 803 fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval } 804 }] 805 ) 806 ); 807 } 808 809 it("project with ascii file names", () => { 810 verifyFileNames("/User/userName/Projects/I", "/user/username/projects/i"); 811 }); 812 813 it("project with ascii file names with i", () => { 814 verifyFileNames("/User/userName/Projects/i", "/user/username/projects/i"); 815 }); 816 817 it("project with unicode file names", () => { 818 verifyFileNames("/User/userName/Projects/İ", "/user/username/projects/İ"); 819 }); 820 }); 821} 822