1namespace ts.projectSystem { 2 export function createHostWithSolutionBuild(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { 3 const host = createServerHost(files); 4 // ts build should succeed 5 tscWatch.ensureErrorFreeBuild(host, rootNames); 6 return host; 7 } 8 9 describe("unittests:: tsserver:: with project references and tsbuild", () => { 10 describe("with container project", () => { 11 function getProjectFiles(project: string): [File, File] { 12 return [ 13 TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), 14 TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), 15 ]; 16 } 17 18 const project = "container"; 19 const containerLib = getProjectFiles("container/lib"); 20 const containerExec = getProjectFiles("container/exec"); 21 const containerCompositeExec = getProjectFiles("container/compositeExec"); 22 const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); 23 const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; 24 25 it("does not error on container only project", () => { 26 const host = createHostWithSolutionBuild(files, [containerConfig.path]); 27 28 // Open external project for the folder 29 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 30 const service = session.getProjectService(); 31 service.openExternalProjects([{ 32 projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), 33 rootFiles: files.map(f => ({ fileName: f.path })), 34 options: {} 35 }]); 36 files.forEach(f => { 37 const args: protocol.FileRequestArgs = { 38 file: f.path, 39 projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined 40 }; 41 session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({ 42 command: protocol.CommandTypes.SyntacticDiagnosticsSync, 43 arguments: args 44 }); 45 session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({ 46 command: protocol.CommandTypes.SemanticDiagnosticsSync, 47 arguments: args 48 }); 49 }); 50 const containerProject = service.configuredProjects.get(containerConfig.path)!; 51 session.executeCommandSeq<protocol.CompilerOptionsDiagnosticsRequest>({ 52 command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, 53 arguments: { projectFileName: containerProject.projectName } 54 }); 55 baselineTsserverLogs("projectReferences", `does not error on container only project`, session); 56 }); 57 58 it("can successfully find references with --out options", () => { 59 const host = createHostWithSolutionBuild(files, [containerConfig.path]); 60 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 61 openFilesForSession([containerCompositeExec[1]], session); 62 const myConstStart = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); 63 session.executeCommandSeq<protocol.RenameRequest>({ 64 command: protocol.CommandTypes.Rename, 65 arguments: { file: containerCompositeExec[1].path, ...myConstStart } 66 }); 67 68 baselineTsserverLogs("projectReferences", `can successfully find references with out option`, session); 69 }); 70 71 it("ancestor and project ref management", () => { 72 const tempFile: File = { 73 path: `/user/username/projects/temp/temp.ts`, 74 content: "let x = 10" 75 }; 76 const host = createHostWithSolutionBuild(files.concat([tempFile]), [containerConfig.path]); 77 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 78 openFilesForSession([containerCompositeExec[1]], session); 79 const service = session.getProjectService(); 80 81 // Open temp file and verify all projects alive 82 openFilesForSession([tempFile], session); 83 84 // Ref projects are loaded after as part of this command 85 const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); 86 session.executeCommandSeq<protocol.RenameRequest>({ 87 command: protocol.CommandTypes.Rename, 88 arguments: { 89 file: containerCompositeExec[1].path, 90 ...locationOfMyConst 91 } 92 }); 93 94 // Open temp file and verify all projects alive 95 service.closeClientFile(tempFile.path); 96 openFilesForSession([tempFile], session); 97 98 // Close all files and open temp file, only inferred project should be alive 99 service.closeClientFile(containerCompositeExec[1].path); 100 service.closeClientFile(tempFile.path); 101 openFilesForSession([tempFile], session); 102 baselineTsserverLogs("projectReferences", `ancestor and project ref management`, session); 103 }); 104 }); 105 106 describe("when root file is file from referenced project", () => { 107 function verify(disableSourceOfProjectReferenceRedirect: boolean) { 108 const projectLocation = `/user/username/projects/project`; 109 const commonConfig: File = { 110 path: `${projectLocation}/src/common/tsconfig.json`, 111 content: JSON.stringify({ 112 compilerOptions: { 113 composite: true, 114 declarationMap: true, 115 outDir: "../../out", 116 baseUrl: "..", 117 disableSourceOfProjectReferenceRedirect 118 }, 119 include: ["./**/*"] 120 }) 121 }; 122 const keyboardTs: File = { 123 path: `${projectLocation}/src/common/input/keyboard.ts`, 124 content: `function bar() { return "just a random function so .d.ts location doesnt match"; } 125export function evaluateKeyboardEvent() { }` 126 }; 127 const keyboardTestTs: File = { 128 path: `${projectLocation}/src/common/input/keyboard.test.ts`, 129 content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; 130function testEvaluateKeyboardEvent() { 131 return evaluateKeyboardEvent(); 132} 133` 134 }; 135 const srcConfig: File = { 136 path: `${projectLocation}/src/tsconfig.json`, 137 content: JSON.stringify({ 138 compilerOptions: { 139 composite: true, 140 declarationMap: true, 141 outDir: "../out", 142 baseUrl: ".", 143 paths: { 144 "common/*": ["./common/*"], 145 }, 146 tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo", 147 disableSourceOfProjectReferenceRedirect 148 }, 149 include: ["./**/*"], 150 references: [ 151 { path: "./common" } 152 ] 153 }) 154 }; 155 const terminalTs: File = { 156 path: `${projectLocation}/src/terminal.ts`, 157 content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; 158function foo() { 159 return evaluateKeyboardEvent(); 160} 161` 162 }; 163 const host = createHostWithSolutionBuild( 164 [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], 165 [srcConfig.path] 166 ); 167 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 168 openFilesForSession([keyboardTs, terminalTs], session); 169 170 const searchStr = "evaluateKeyboardEvent"; 171 const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`; 172 const result = session.executeCommandSeq<protocol.ReferencesRequest>({ 173 command: protocol.CommandTypes.References, 174 arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr) 175 }).response as protocol.ReferencesResponseBody; 176 assert.deepEqual(result, { 177 refs: [ 178 makeReferenceItem({ 179 file: keyboardTs, 180 text: searchStr, 181 contextText: `export function evaluateKeyboardEvent() { }`, 182 isDefinition: true, 183 lineText: `export function evaluateKeyboardEvent() { }` 184 }), 185 makeReferenceItem({ 186 file: keyboardTestTs, 187 text: searchStr, 188 contextText: importStr, 189 isDefinition: false, 190 isWriteAccess: true, 191 lineText: importStr 192 }), 193 makeReferenceItem({ 194 file: keyboardTestTs, 195 text: searchStr, 196 options: { index: 1 }, 197 isDefinition: false, 198 lineText: ` return evaluateKeyboardEvent();` 199 }), 200 makeReferenceItem({ 201 file: terminalTs, 202 text: searchStr, 203 contextText: importStr, 204 isDefinition: false, 205 isWriteAccess: true, 206 lineText: importStr 207 }), 208 makeReferenceItem({ 209 file: terminalTs, 210 text: searchStr, 211 options: { index: 1 }, 212 isDefinition: false, 213 lineText: ` return evaluateKeyboardEvent();` 214 }), 215 ], 216 symbolName: searchStr, 217 symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset, 218 symbolDisplayString: "function evaluateKeyboardEvent(): void" 219 }); 220 baselineTsserverLogs("projectReferences", `root file is file from referenced project${disableSourceOfProjectReferenceRedirect ? " and using declaration maps" : ""}`, session); 221 } 222 223 it(`when using declaration file maps to navigate between projects`, () => { 224 verify(/*disableSourceOfProjectReferenceRedirect*/ true); 225 }); 226 it(`when using original source files in the project`, () => { 227 verify(/*disableSourceOfProjectReferenceRedirect*/ false); 228 }); 229 }); 230 231 it("reusing d.ts files from composite and non composite projects", () => { 232 const configA: File = { 233 path: `${tscWatch.projectRoot}/compositea/tsconfig.json`, 234 content: JSON.stringify({ 235 compilerOptions: { 236 composite: true, 237 outDir: "../dist/", 238 rootDir: "../", 239 baseUrl: "../", 240 paths: { "@ref/*": ["./dist/*"] } 241 } 242 }) 243 }; 244 const aTs: File = { 245 path: `${tscWatch.projectRoot}/compositea/a.ts`, 246 content: `import { b } from "@ref/compositeb/b";` 247 }; 248 const a2Ts: File = { 249 path: `${tscWatch.projectRoot}/compositea/a2.ts`, 250 content: `export const x = 10;` 251 }; 252 const configB: File = { 253 path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`, 254 content: configA.content 255 }; 256 const bTs: File = { 257 path: `${tscWatch.projectRoot}/compositeb/b.ts`, 258 content: "export function b() {}" 259 }; 260 const bDts: File = { 261 path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`, 262 content: "export declare function b(): void;" 263 }; 264 const configC: File = { 265 path: `${tscWatch.projectRoot}/compositec/tsconfig.json`, 266 content: JSON.stringify({ 267 compilerOptions: { 268 composite: true, 269 outDir: "../dist/", 270 rootDir: "../", 271 baseUrl: "../", 272 paths: { "@ref/*": ["./*"] } 273 }, 274 references: [{ path: "../compositeb" }] 275 }) 276 }; 277 const cTs: File = { 278 path: `${tscWatch.projectRoot}/compositec/c.ts`, 279 content: aTs.content 280 }; 281 const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; 282 const host = createServerHost(files); 283 const service = createProjectService(host); 284 service.openClientFile(aTs.path); 285 service.checkNumberOfProjects({ configuredProjects: 1 }); 286 287 // project A referencing b.d.ts without project reference 288 const projectA = service.configuredProjects.get(configA.path)!; 289 assert.isDefined(projectA); 290 checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]); 291 292 // reuses b.d.ts but sets the path and resolved path since projectC has project references 293 // as the real resolution was to b.ts 294 service.openClientFile(cTs.path); 295 service.checkNumberOfProjects({ configuredProjects: 2 }); 296 const projectC = service.configuredProjects.get(configC.path)!; 297 checkProjectActualFiles(projectC, [cTs.path, bTs.path, libFile.path, configC.path]); 298 299 // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location 300 host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); 301 assert.isTrue(projectA.dirty); 302 projectA.updateGraph(); 303 }); 304 305 describe("when references are monorepo like with symlinks", () => { 306 interface Packages { 307 bPackageJson: File; 308 aTest: File; 309 bFoo: File; 310 bBar: File; 311 bSymlink: SymLink; 312 } 313 function verifySymlinkScenario(scenario: string, packages: () => Packages) { 314 describe(`${scenario}: when solution is not built`, () => { 315 it("with preserveSymlinks turned off", () => { 316 verifySession(scenario, packages(), /*alreadyBuilt*/ false, {}); 317 }); 318 319 it("with preserveSymlinks turned on", () => { 320 verifySession(scenario, packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true }); 321 }); 322 }); 323 324 describe(`${scenario}: when solution is already built`, () => { 325 it("with preserveSymlinks turned off", () => { 326 verifySession(scenario, packages(), /*alreadyBuilt*/ true, {}); 327 }); 328 329 it("with preserveSymlinks turned on", () => { 330 verifySession(scenario, packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true }); 331 }); 332 }); 333 } 334 335 function verifySession(scenario: string, { bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) { 336 const aConfig = config("A", extraOptions, ["../B"]); 337 const bConfig = config("B", extraOptions); 338 const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; 339 const host = alreadyBuilt ? 340 createHostWithSolutionBuild(files, [aConfig.path]) : 341 createServerHost(files); 342 343 // Create symlink in node module 344 const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); 345 openFilesForSession([aTest], session); 346 verifyGetErrRequest({ session, host, files: [aTest] }); 347 session.executeCommandSeq<protocol.UpdateOpenRequest>({ 348 command: protocol.CommandTypes.UpdateOpen, 349 arguments: { 350 changedFiles: [{ 351 fileName: aTest.path, 352 textChanges: [{ 353 newText: "\n", 354 start: { line: 5, offset: 1 }, 355 end: { line: 5, offset: 1 } 356 }] 357 }] 358 } 359 }); 360 verifyGetErrRequest({ session, host, files: [aTest] }); 361 baselineTsserverLogs("projectReferences", `monorepo like with symlinks ${scenario} and solution is ${alreadyBuilt ? "built" : "not built"}${extraOptions.preserveSymlinks ? " with preserveSymlinks" : ""}`, session); 362 } 363 364 function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { 365 return { 366 path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, 367 content: JSON.stringify({ 368 compilerOptions: { 369 outDir: "lib", 370 rootDir: "src", 371 composite: true, 372 ...extraOptions 373 }, 374 include: ["src"], 375 ...(references ? { references: references.map(path => ({ path })) } : {}) 376 }) 377 }; 378 } 379 380 function file(packageName: string, fileName: string, content: string): File { 381 return { 382 path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, 383 content 384 }; 385 } 386 387 function verifyMonoRepoLike(scope = "") { 388 verifySymlinkScenario(`when packageJson has types field and has index.ts${scope ? " with scoped package" : ""}`, () => ({ 389 bPackageJson: { 390 path: `${tscWatch.projectRoot}/packages/B/package.json`, 391 content: JSON.stringify({ 392 main: "lib/index.js", 393 types: "lib/index.d.ts" 394 }) 395 }, 396 aTest: file("A", "index.ts", `import { foo } from '${scope}b'; 397import { bar } from '${scope}b/lib/bar'; 398foo(); 399bar(); 400`), 401 bFoo: file("B", "index.ts", `export function foo() { }`), 402 bBar: file("B", "bar.ts", `export function bar() { }`), 403 bSymlink: { 404 path: `${tscWatch.projectRoot}/node_modules/${scope}b`, 405 symLink: `${tscWatch.projectRoot}/packages/B` 406 } 407 })); 408 409 verifySymlinkScenario(`when referencing file from subFolder${scope ? " with scoped package" : ""}`, () => ({ 410 bPackageJson: { 411 path: `${tscWatch.projectRoot}/packages/B/package.json`, 412 content: "{}" 413 }, 414 aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; 415import { bar } from '${scope}b/lib/bar/foo'; 416foo(); 417bar(); 418`), 419 bFoo: file("B", "foo.ts", `export function foo() { }`), 420 bBar: file("B", "bar/foo.ts", `export function bar() { }`), 421 bSymlink: { 422 path: `${tscWatch.projectRoot}/node_modules/${scope}b`, 423 symLink: `${tscWatch.projectRoot}/packages/B` 424 } 425 })); 426 } 427 428 describe("when package is not scoped", () => { 429 verifyMonoRepoLike(); 430 }); 431 describe("when package is scoped", () => { 432 verifyMonoRepoLike("@issue/"); 433 }); 434 }); 435 436 it("when the referenced projects have allowJs and emitDeclarationOnly", () => { 437 const compositeConfig: File = { 438 path: `${tscWatch.projectRoot}/packages/emit-composite/tsconfig.json`, 439 content: JSON.stringify({ 440 compilerOptions: { 441 composite: true, 442 allowJs: true, 443 emitDeclarationOnly: true, 444 outDir: "lib", 445 rootDir: "src" 446 }, 447 include: ["src"] 448 }) 449 }; 450 const compositePackageJson: File = { 451 path: `${tscWatch.projectRoot}/packages/emit-composite/package.json`, 452 content: JSON.stringify({ 453 name: "emit-composite", 454 version: "1.0.0", 455 main: "src/index.js", 456 typings: "lib/index.d.ts" 457 }) 458 }; 459 const compositeIndex: File = { 460 path: `${tscWatch.projectRoot}/packages/emit-composite/src/index.js`, 461 content: `const testModule = require('./testModule'); 462module.exports = { 463 ...testModule 464}` 465 }; 466 const compositeTestModule: File = { 467 path: `${tscWatch.projectRoot}/packages/emit-composite/src/testModule.js`, 468 content: `/** 469 * @param {string} arg 470 */ 471 const testCompositeFunction = (arg) => { 472} 473module.exports = { 474 testCompositeFunction 475}` 476 }; 477 const consumerConfig: File = { 478 path: `${tscWatch.projectRoot}/packages/consumer/tsconfig.json`, 479 content: JSON.stringify({ 480 include: ["src"], 481 references: [{ path: "../emit-composite" }] 482 }) 483 }; 484 const consumerIndex: File = { 485 path: `${tscWatch.projectRoot}/packages/consumer/src/index.ts`, 486 content: `import { testCompositeFunction } from 'emit-composite'; 487testCompositeFunction('why hello there'); 488testCompositeFunction('why hello there', 42);` 489 }; 490 const symlink: SymLink = { 491 path: `${tscWatch.projectRoot}/node_modules/emit-composite`, 492 symLink: `${tscWatch.projectRoot}/packages/emit-composite` 493 }; 494 const host = createServerHost([libFile, compositeConfig, compositePackageJson, compositeIndex, compositeTestModule, consumerConfig, consumerIndex, symlink], { useCaseSensitiveFileNames: true }); 495 const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); 496 openFilesForSession([consumerIndex], session); 497 verifyGetErrRequest({ host, session, files: [consumerIndex] }); 498 baselineTsserverLogs("projectReferences", `when the referenced projects have allowJs and emitDeclarationOnly`, session); 499 }); 500 501 it("when finding local reference doesnt load ancestor/sibling projects", () => { 502 const solutionLocation = "/user/username/projects/solution"; 503 const solution: File = { 504 path: `${solutionLocation}/tsconfig.json`, 505 content: JSON.stringify({ 506 files: [], 507 include: [], 508 references: [ 509 { path: "./compiler" }, 510 { path: "./services" }, 511 ] 512 }) 513 }; 514 const compilerConfig: File = { 515 path: `${solutionLocation}/compiler/tsconfig.json`, 516 content: JSON.stringify({ 517 compilerOptions: { 518 composite: true, 519 module: "none" 520 }, 521 files: ["./types.ts", "./program.ts"] 522 }) 523 }; 524 const typesFile: File = { 525 path: `${solutionLocation}/compiler/types.ts`, 526 content: ` 527 namespace ts { 528 export interface Program { 529 getSourceFiles(): string[]; 530 } 531 }` 532 }; 533 const programFile: File = { 534 path: `${solutionLocation}/compiler/program.ts`, 535 content: ` 536 namespace ts { 537 export const program: Program = { 538 getSourceFiles: () => [getSourceFile()] 539 }; 540 function getSourceFile() { return "something"; } 541 }` 542 }; 543 const servicesConfig: File = { 544 path: `${solutionLocation}/services/tsconfig.json`, 545 content: JSON.stringify({ 546 compilerOptions: { 547 composite: true 548 }, 549 files: ["./services.ts"], 550 references: [ 551 { path: "../compiler" } 552 ] 553 }) 554 }; 555 const servicesFile: File = { 556 path: `${solutionLocation}/services/services.ts`, 557 content: ` 558 namespace ts { 559 const result = program.getSourceFiles(); 560 }` 561 }; 562 563 const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; 564 const host = createServerHost(files); 565 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 566 openFilesForSession([programFile], session); 567 568 // Find all references for getSourceFile 569 // Shouldnt load more projects 570 session.executeCommandSeq<protocol.ReferencesRequest>({ 571 command: protocol.CommandTypes.References, 572 arguments: protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 }) 573 }); 574 575 // Find all references for getSourceFiles 576 // Should load more projects 577 session.executeCommandSeq<protocol.ReferencesRequest>({ 578 command: protocol.CommandTypes.References, 579 arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") 580 }); 581 baselineTsserverLogs("projectReferences", `finding local reference doesnt load ancestor/sibling projects`, session); 582 }); 583 584 it("when finding references in overlapping projects", () => { 585 const solutionLocation = "/user/username/projects/solution"; 586 const solutionConfig: File = { 587 path: `${solutionLocation}/tsconfig.json`, 588 content: JSON.stringify({ 589 files: [], 590 include: [], 591 references: [ 592 { path: "./a" }, 593 { path: "./b" }, 594 { path: "./c" }, 595 { path: "./d" }, 596 ] 597 }) 598 }; 599 const aConfig: File = { 600 path: `${solutionLocation}/a/tsconfig.json`, 601 content: JSON.stringify({ 602 compilerOptions: { 603 composite: true, 604 module: "none" 605 }, 606 files: ["./index.ts"] 607 }) 608 }; 609 const aFile: File = { 610 path: `${solutionLocation}/a/index.ts`, 611 content: ` 612 export interface I { 613 M(): void; 614 }` 615 }; 616 617 const bConfig: File = { 618 path: `${solutionLocation}/b/tsconfig.json`, 619 content: JSON.stringify({ 620 compilerOptions: { 621 composite: true 622 }, 623 files: ["./index.ts"], 624 references: [ 625 { path: "../a" } 626 ] 627 }) 628 }; 629 const bFile: File = { 630 path: `${solutionLocation}/b/index.ts`, 631 content: ` 632 import { I } from "../a"; 633 634 export class B implements I { 635 M() {} 636 }` 637 }; 638 639 const cConfig: File = { 640 path: `${solutionLocation}/c/tsconfig.json`, 641 content: JSON.stringify({ 642 compilerOptions: { 643 composite: true 644 }, 645 files: ["./index.ts"], 646 references: [ 647 { path: "../b" } 648 ] 649 }) 650 }; 651 const cFile: File = { 652 path: `${solutionLocation}/c/index.ts`, 653 content: ` 654 import { I } from "../a"; 655 import { B } from "../b"; 656 657 export const C: I = new B(); 658 ` 659 }; 660 661 const dConfig: File = { 662 path: `${solutionLocation}/d/tsconfig.json`, 663 content: JSON.stringify({ 664 compilerOptions: { 665 composite: true 666 }, 667 files: ["./index.ts"], 668 references: [ 669 { path: "../c" } 670 ] 671 }) 672 }; 673 const dFile: File = { 674 path: `${solutionLocation}/d/index.ts`, 675 content: ` 676 import { I } from "../a"; 677 import { C } from "../c"; 678 679 export const D: I = C; 680 ` 681 }; 682 683 const files = [libFile, solutionConfig, aConfig, aFile, bConfig, bFile, cConfig, cFile, dConfig, dFile, libFile]; 684 const host = createServerHost(files); 685 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 686 openFilesForSession([bFile], session); 687 688 // The first search will trigger project loads 689 session.executeCommandSeq<protocol.ReferencesRequest>({ 690 command: protocol.CommandTypes.References, 691 arguments: protocolFileLocationFromSubstring(bFile, "I", { index: 1 }) 692 }); 693 694 // The second search starts with the projects already loaded 695 // Formerly, this would search some projects multiple times 696 session.executeCommandSeq<protocol.ReferencesRequest>({ 697 command: protocol.CommandTypes.References, 698 arguments: protocolFileLocationFromSubstring(bFile, "I", { index: 1 }) 699 }); 700 701 baselineTsserverLogs("projectReferences", `finding references in overlapping projects`, session); 702 }); 703 704 describe("special handling of localness of the definitions for findAllRefs", () => { 705 function verify(scenario: string, definition: string, usage: string, referenceTerm: string) { 706 it(scenario, () => { 707 const solutionLocation = "/user/username/projects/solution"; 708 const solution: File = { 709 path: `${solutionLocation}/tsconfig.json`, 710 content: JSON.stringify({ 711 files: [], 712 references: [ 713 { path: "./api" }, 714 { path: "./app" }, 715 ] 716 }) 717 }; 718 const apiConfig: File = { 719 path: `${solutionLocation}/api/tsconfig.json`, 720 content: JSON.stringify({ 721 compilerOptions: { 722 composite: true, 723 outDir: "dist", 724 rootDir: "src", 725 }, 726 include: ["src"], 727 references: [{ path: "../shared" }] 728 }) 729 }; 730 const apiFile: File = { 731 path: `${solutionLocation}/api/src/server.ts`, 732 content: `import * as shared from "../../shared/dist"; 733${usage}` 734 }; 735 const appConfig: File = { 736 path: `${solutionLocation}/app/tsconfig.json`, 737 content: apiConfig.content 738 }; 739 const appFile: File = { 740 path: `${solutionLocation}/app/src/app.ts`, 741 content: apiFile.content 742 }; 743 const sharedConfig: File = { 744 path: `${solutionLocation}/shared/tsconfig.json`, 745 content: JSON.stringify({ 746 compilerOptions: { 747 composite: true, 748 outDir: "dist", 749 rootDir: "src", 750 }, 751 include: ["src"] 752 }) 753 }; 754 const sharedFile: File = { 755 path: `${solutionLocation}/shared/src/index.ts`, 756 content: definition 757 }; 758 const host = createServerHost([libFile, solution, libFile, apiConfig, apiFile, appConfig, appFile, sharedConfig, sharedFile]); 759 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 760 openFilesForSession([apiFile], session); 761 762 // Find all references 763 session.executeCommandSeq<protocol.ReferencesRequest>({ 764 command: protocol.CommandTypes.References, 765 arguments: protocolFileLocationFromSubstring(apiFile, referenceTerm) 766 }); 767 768 baselineTsserverLogs("projectReferences", `special handling of localness ${scenario}`, session); 769 }); 770 } 771 772 verify( 773 "when using arrow function assignment", 774 `export const dog = () => { };`, 775 `shared.dog();`, 776 "dog" 777 ); 778 779 verify( 780 "when using arrow function as object literal property types", 781 `export const foo = { bar: () => { } };`, 782 `shared.foo.bar();`, 783 "bar" 784 ); 785 786 verify( 787 "when using object literal property", 788 `export const foo = { baz: "BAZ" };`, 789 `shared.foo.baz;`, 790 "baz" 791 ); 792 793 verify( 794 "when using method of class expression", 795 `export const foo = class { fly() {} };`, 796 `const instance = new shared.foo(); 797instance.fly();`, 798 "fly" 799 ); 800 801 802 verify( 803 // when using arrow function as object literal property is loaded through indirect assignment with original declaration local to project is treated as local 804 "when using arrow function as object literal property", 805 `const local = { bar: () => { } }; 806export const foo = local;`, 807 `shared.foo.bar();`, 808 "bar" 809 ); 810 }); 811 812 it("when disableSolutionSearching is true, solution and siblings are not loaded", () => { 813 const solutionLocation = "/user/username/projects/solution"; 814 const solution: File = { 815 path: `${solutionLocation}/tsconfig.json`, 816 content: JSON.stringify({ 817 files: [], 818 include: [], 819 references: [ 820 { path: "./compiler" }, 821 { path: "./services" }, 822 ] 823 }) 824 }; 825 const compilerConfig: File = { 826 path: `${solutionLocation}/compiler/tsconfig.json`, 827 content: JSON.stringify({ 828 compilerOptions: { 829 composite: true, 830 module: "none", 831 disableSolutionSearching: true 832 }, 833 files: ["./types.ts", "./program.ts"] 834 }) 835 }; 836 const typesFile: File = { 837 path: `${solutionLocation}/compiler/types.ts`, 838 content: ` 839 namespace ts { 840 export interface Program { 841 getSourceFiles(): string[]; 842 } 843 }` 844 }; 845 const programFile: File = { 846 path: `${solutionLocation}/compiler/program.ts`, 847 content: ` 848 namespace ts { 849 export const program: Program = { 850 getSourceFiles: () => [getSourceFile()] 851 }; 852 function getSourceFile() { return "something"; } 853 }` 854 }; 855 const servicesConfig: File = { 856 path: `${solutionLocation}/services/tsconfig.json`, 857 content: JSON.stringify({ 858 compilerOptions: { 859 composite: true 860 }, 861 files: ["./services.ts"], 862 references: [ 863 { path: "../compiler" } 864 ] 865 }) 866 }; 867 const servicesFile: File = { 868 path: `${solutionLocation}/services/services.ts`, 869 content: ` 870 namespace ts { 871 const result = program.getSourceFiles(); 872 }` 873 }; 874 875 const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; 876 const host = createServerHost(files); 877 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 878 openFilesForSession([programFile], session); 879 880 // Find all references 881 // No new solutions/projects loaded 882 session.executeCommandSeq<protocol.ReferencesRequest>({ 883 command: protocol.CommandTypes.References, 884 arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") 885 }); 886 baselineTsserverLogs("projectReferences", `with disableSolutionSearching solution and siblings are not loaded`, session); 887 }); 888 889 describe("when default project is solution project", () => { 890 interface Setup { 891 scenario: string; 892 solutionOptions?: CompilerOptions; 893 solutionFiles?: string[]; 894 configRefs: string[]; 895 additionalFiles: readonly File[]; 896 } 897 const main: File = { 898 path: `${tscWatch.projectRoot}/src/main.ts`, 899 content: `import { foo } from 'helpers/functions'; 900export { foo };` 901 }; 902 const helper: File = { 903 path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, 904 content: `export const foo = 1;` 905 }; 906 const mainDts: File = { 907 path: `${tscWatch.projectRoot}/target/src/main.d.ts`, 908 content: `import { foo } from 'helpers/functions'; 909export { foo }; 910//# sourceMappingURL=main.d.ts.map` 911 }; 912 const mainDtsMap: File = { 913 path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`, 914 content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}` 915 }; 916 const helperDts: File = { 917 path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, 918 content: `export declare const foo = 1; 919//# sourceMappingURL=functions.d.ts.map` 920 }; 921 const helperDtsMap: File = { 922 path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, 923 content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` 924 }; 925 const tsconfigIndirect3: File = { 926 path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`, 927 content: JSON.stringify({ 928 compilerOptions: { 929 baseUrl: "../target/src/" 930 }, 931 }) 932 }; 933 const fileResolvingToMainDts: File = { 934 path: `${tscWatch.projectRoot}/indirect3/main.ts`, 935 content: `import { foo } from 'main'; 936foo; 937export function bar() {}` 938 }; 939 const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; 940 const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; 941 const dummyFilePath = "/dummy/dummy.ts"; 942 function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles }: Setup) { 943 const tsconfigSrc: File = { 944 path: tsconfigSrcPath, 945 content: JSON.stringify({ 946 compilerOptions: { 947 composite: true, 948 outDir: "./target/", 949 baseUrl: "./src/" 950 }, 951 include: ["./src/**/*"] 952 }) 953 }; 954 const tsconfig: File = { 955 path: tsconfigPath, 956 content: JSON.stringify({ 957 ... (solutionOptions ? { compilerOptions: solutionOptions } : {}), 958 references: configRefs.map(path => ({ path })), 959 files: solutionFiles || [] 960 }) 961 }; 962 const dummyFile: File = { 963 path: dummyFilePath, 964 content: "let a = 10;" 965 }; 966 const host = createServerHost([ 967 tsconfigSrc, tsconfig, main, helper, 968 libFile, dummyFile, 969 mainDts, mainDtsMap, helperDts, helperDtsMap, 970 tsconfigIndirect3, fileResolvingToMainDts, 971 ...additionalFiles]); 972 const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) }); 973 const service = session.getProjectService(); 974 service.openClientFile(main.path); 975 return { session, service, host }; 976 } 977 978 function verifySolutionScenario(input: Setup) { 979 const { session, service, host } = setup(input); 980 981 const info = service.getScriptInfoForPath(main.path as Path)!; 982 session.logger.startGroup(); 983 session.logger.info(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); 984 session.logger.info(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)!.projectName}`); 985 session.logger.endGroup(); 986 987 // Verify errors 988 verifyGetErrRequest({ session, host, files: [main] }); 989 990 // Verify collection of script infos 991 service.openClientFile(dummyFilePath); 992 993 service.closeClientFile(main.path); 994 service.closeClientFile(dummyFilePath); 995 service.openClientFile(dummyFilePath); 996 997 service.openClientFile(main.path); 998 service.closeClientFile(dummyFilePath); 999 service.openClientFile(dummyFilePath); 1000 1001 // Verify Reload projects 1002 service.reloadProjects(); 1003 1004 // Find all refs 1005 session.executeCommandSeq<protocol.ReferencesRequest>({ 1006 command: protocol.CommandTypes.References, 1007 arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 }) 1008 }).response as protocol.ReferencesResponseBody; 1009 1010 service.closeClientFile(main.path); 1011 service.closeClientFile(dummyFilePath); 1012 1013 // Verify when declaration map references the file 1014 service.openClientFile(fileResolvingToMainDts.path); 1015 1016 // Find all refs from dts include 1017 session.executeCommandSeq<protocol.ReferencesRequest>({ 1018 command: protocol.CommandTypes.References, 1019 arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") 1020 }).response as protocol.ReferencesResponseBody; 1021 baselineTsserverLogs("projectReferences", input.scenario, session); 1022 } 1023 1024 function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) { 1025 const tsconfigIndirect: File = { 1026 path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, 1027 content: JSON.stringify({ 1028 compilerOptions: { 1029 composite: true, 1030 outDir: "./target/", 1031 baseUrl: "./src/", 1032 ...optionsToExtend 1033 }, 1034 files: [`./indirect${postfix}/main.ts`], 1035 references: [{ path: "./tsconfig-src.json" }] 1036 }) 1037 }; 1038 const indirect: File = { 1039 path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, 1040 content: fileResolvingToMainDts.content 1041 }; 1042 return { tsconfigIndirect, indirect }; 1043 } 1044 1045 function verifyDisableReferencedProjectLoad(input: Setup) { 1046 const { session, service } = setup(input); 1047 1048 const info = service.getScriptInfoForPath(main.path as Path)!; 1049 session.logger.startGroup(); 1050 session.logger.info(`getDefaultProject for ${main.path}: ${info.getDefaultProject().projectName}`); 1051 session.logger.info(`findDefaultConfiguredProject for ${main.path}: ${service.findDefaultConfiguredProject(info)?.projectName}`); 1052 session.logger.endGroup(); 1053 1054 // Verify collection of script infos 1055 service.openClientFile(dummyFilePath); 1056 1057 service.closeClientFile(main.path); 1058 service.closeClientFile(dummyFilePath); 1059 service.openClientFile(dummyFilePath); 1060 1061 service.openClientFile(main.path); 1062 1063 // Verify Reload projects 1064 service.reloadProjects(); 1065 baselineTsserverLogs("projectReferences", input.scenario, session); 1066 } 1067 1068 it("when project is directly referenced by solution", () => { 1069 verifySolutionScenario({ 1070 scenario: "project is directly referenced by solution", 1071 configRefs: ["./tsconfig-src.json"], 1072 additionalFiles: emptyArray, 1073 }); 1074 }); 1075 1076 it("when project is indirectly referenced by solution", () => { 1077 const { tsconfigIndirect, indirect } = getIndirectProject("1"); 1078 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 1079 verifySolutionScenario({ 1080 scenario: "project is indirectly referenced by solution", 1081 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 1082 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], 1083 }); 1084 }); 1085 1086 it("disables looking into the child project if disableReferencedProjectLoad is set", () => { 1087 verifyDisableReferencedProjectLoad({ 1088 scenario: "disables looking into the child project if disableReferencedProjectLoad is set", 1089 solutionOptions: { disableReferencedProjectLoad: true }, 1090 configRefs: ["./tsconfig-src.json"], 1091 additionalFiles: emptyArray, 1092 }); 1093 }); 1094 1095 it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { 1096 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 1097 verifyDisableReferencedProjectLoad({ 1098 scenario: "disables looking into the child project if disableReferencedProjectLoad is set in indirect project", 1099 configRefs: ["./tsconfig-indirect1.json"], 1100 additionalFiles: [tsconfigIndirect, indirect], 1101 }); 1102 }); 1103 1104 it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { 1105 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 1106 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 1107 verifyDisableReferencedProjectLoad({ 1108 scenario: "disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", 1109 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 1110 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], 1111 }); 1112 }); 1113 1114 describe("when solution is project that contains its own files", () => { 1115 it("when the project found is not solution but references open file through project reference", () => { 1116 const ownMain: File = { 1117 path: `${tscWatch.projectRoot}/own/main.ts`, 1118 content: fileResolvingToMainDts.content 1119 }; 1120 verifySolutionScenario({ 1121 scenario: "solution with its own files and project found is not solution but references open file through project reference", 1122 solutionFiles: [`./own/main.ts`], 1123 solutionOptions: { 1124 outDir: "./target/", 1125 baseUrl: "./src/" 1126 }, 1127 configRefs: ["./tsconfig-src.json"], 1128 additionalFiles: [ownMain], 1129 }); 1130 }); 1131 1132 it("when project is indirectly referenced by solution", () => { 1133 const ownMain: File = { 1134 path: `${tscWatch.projectRoot}/own/main.ts`, 1135 content: `import { bar } from 'main'; 1136bar;` 1137 }; 1138 const { tsconfigIndirect, indirect } = getIndirectProject("1"); 1139 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 1140 verifySolutionScenario({ 1141 scenario: "solution with its own files and project is indirectly referenced by solution", 1142 solutionFiles: [`./own/main.ts`], 1143 solutionOptions: { 1144 outDir: "./target/", 1145 baseUrl: "./indirect1/" 1146 }, 1147 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 1148 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], 1149 }); 1150 }); 1151 1152 it("disables looking into the child project if disableReferencedProjectLoad is set", () => { 1153 const ownMain: File = { 1154 path: `${tscWatch.projectRoot}/own/main.ts`, 1155 content: fileResolvingToMainDts.content 1156 }; 1157 verifyDisableReferencedProjectLoad({ 1158 scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set", 1159 solutionFiles: [`./own/main.ts`], 1160 solutionOptions: { 1161 outDir: "./target/", 1162 baseUrl: "./src/", 1163 disableReferencedProjectLoad: true 1164 }, 1165 configRefs: ["./tsconfig-src.json"], 1166 additionalFiles: [ownMain], 1167 }); 1168 }); 1169 1170 it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { 1171 const ownMain: File = { 1172 path: `${tscWatch.projectRoot}/own/main.ts`, 1173 content: `import { bar } from 'main'; 1174bar;` 1175 }; 1176 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 1177 verifyDisableReferencedProjectLoad({ 1178 scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in indirect project", 1179 solutionFiles: [`./own/main.ts`], 1180 solutionOptions: { 1181 outDir: "./target/", 1182 baseUrl: "./indirect1/", 1183 }, 1184 configRefs: ["./tsconfig-indirect1.json"], 1185 additionalFiles: [tsconfigIndirect, indirect, ownMain], 1186 }); 1187 }); 1188 1189 it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { 1190 const ownMain: File = { 1191 path: `${tscWatch.projectRoot}/own/main.ts`, 1192 content: `import { bar } from 'main'; 1193bar;` 1194 }; 1195 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 1196 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 1197 verifyDisableReferencedProjectLoad({ 1198 scenario: "solution with its own files and disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", 1199 solutionFiles: [`./own/main.ts`], 1200 solutionOptions: { 1201 outDir: "./target/", 1202 baseUrl: "./indirect1/", 1203 }, 1204 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 1205 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], 1206 }); 1207 }); 1208 }); 1209 }); 1210 1211 describe("when new file is added to the referenced project", () => { 1212 function setup(extendOptionsProject2?: CompilerOptions) { 1213 const config1: File = { 1214 path: `${tscWatch.projectRoot}/projects/project1/tsconfig.json`, 1215 content: JSON.stringify({ 1216 compilerOptions: { 1217 module: "none", 1218 composite: true 1219 }, 1220 exclude: ["temp"] 1221 }) 1222 }; 1223 const class1: File = { 1224 path: `${tscWatch.projectRoot}/projects/project1/class1.ts`, 1225 content: `class class1 {}` 1226 }; 1227 const class1Dts: File = { 1228 path: `${tscWatch.projectRoot}/projects/project1/class1.d.ts`, 1229 content: `declare class class1 {}` 1230 }; 1231 const config2: File = { 1232 path: `${tscWatch.projectRoot}/projects/project2/tsconfig.json`, 1233 content: JSON.stringify({ 1234 compilerOptions: { 1235 module: "none", 1236 composite: true, 1237 ...(extendOptionsProject2 || {}) 1238 }, 1239 references: [ 1240 { path: "../project1" } 1241 ] 1242 }) 1243 }; 1244 const class2: File = { 1245 path: `${tscWatch.projectRoot}/projects/project2/class2.ts`, 1246 content: `class class2 {}` 1247 }; 1248 const host = createServerHost([config1, class1, class1Dts, config2, class2, libFile]); 1249 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 1250 openFilesForSession([class2], session); 1251 return { host, session, class1 }; 1252 } 1253 1254 it("when referenced project is not open", () => { 1255 const { host, session } = setup(); 1256 1257 // Add new class to referenced project 1258 const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; 1259 host.writeFile(class3, `class class3 {}`); 1260 host.checkTimeoutQueueLengthAndRun(2); 1261 1262 // Add excluded file to referenced project 1263 host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); 1264 host.checkTimeoutQueueLengthAndRun(0); 1265 1266 // Add output from new class to referenced project 1267 const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; 1268 host.writeFile(class3Dts, `declare class class3 {}`); 1269 host.checkTimeoutQueueLengthAndRun(0); 1270 baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open`, session); 1271 }); 1272 1273 it("when referenced project is open", () => { 1274 const { host, session, class1 } = setup(); 1275 openFilesForSession([class1], session); 1276 1277 // Add new class to referenced project 1278 const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; 1279 host.writeFile(class3, `class class3 {}`); 1280 host.checkTimeoutQueueLengthAndRun(3); 1281 // Add excluded file to referenced project 1282 host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); 1283 host.checkTimeoutQueueLengthAndRun(0); 1284 // Add output from new class to referenced project 1285 const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; 1286 host.writeFile(class3Dts, `declare class class3 {}`); 1287 host.checkTimeoutQueueLengthAndRun(0); 1288 baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open`, session); 1289 }); 1290 1291 it("when referenced project is not open with disableSourceOfProjectReferenceRedirect", () => { 1292 const { host, session } = setup({ disableSourceOfProjectReferenceRedirect: true }); 1293 1294 // Add new class to referenced project 1295 const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; 1296 host.writeFile(class3, `class class3 {}`); 1297 host.checkTimeoutQueueLengthAndRun(2); 1298 // Add output of new class to referenced project 1299 const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; 1300 host.writeFile(class3Dts, `declare class class3 {}`); 1301 host.checkTimeoutQueueLengthAndRun(2); 1302 // Add excluded file to referenced project 1303 host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); 1304 host.checkTimeoutQueueLengthAndRun(0); 1305 // Delete output from new class to referenced project 1306 host.deleteFile(class3Dts); 1307 host.checkTimeoutQueueLengthAndRun(2); 1308 // Write back output of new class to referenced project 1309 host.writeFile(class3Dts, `declare class class3 {}`); 1310 host.checkTimeoutQueueLengthAndRun(2); 1311 baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is not open with disableSourceOfProjectReferenceRedirect`, session); 1312 }); 1313 1314 it("when referenced project is open with disableSourceOfProjectReferenceRedirect", () => { 1315 const { host, session, class1 } = setup({ disableSourceOfProjectReferenceRedirect: true }); 1316 openFilesForSession([class1], session); 1317 1318 // Add new class to referenced project 1319 const class3 = `${tscWatch.projectRoot}/projects/project1/class3.ts`; 1320 host.writeFile(class3, `class class3 {}`); 1321 host.checkTimeoutQueueLengthAndRun(3); 1322 // Add output of new class to referenced project 1323 const class3Dts = `${tscWatch.projectRoot}/projects/project1/class3.d.ts`; 1324 host.writeFile(class3Dts, `declare class class3 {}`); 1325 host.checkTimeoutQueueLengthAndRun(2); 1326 // Add excluded file to referenced project 1327 host.ensureFileOrFolder({ path: `${tscWatch.projectRoot}/projects/project1/temp/file.d.ts`, content: `declare class file {}` }); 1328 host.checkTimeoutQueueLengthAndRun(0); 1329 // Delete output from new class to referenced project 1330 host.deleteFile(class3Dts); 1331 host.checkTimeoutQueueLengthAndRun(2); 1332 // Write back output of new class to referenced project 1333 host.writeFile(class3Dts, `declare class class3 {}`); 1334 host.checkTimeoutQueueLengthAndRun(2); 1335 baselineTsserverLogs("projectReferences", `new file is added to the referenced project when referenced project is open with disableSourceOfProjectReferenceRedirect`, session); 1336 }); 1337 }); 1338 1339 describe("auto import with referenced project", () => { 1340 function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) { 1341 const solnConfig: File = { 1342 path: `${tscWatch.projectRoot}/tsconfig.json`, 1343 content: JSON.stringify({ 1344 files: [], 1345 references: [ 1346 { path: "shared/src/library" }, 1347 { path: "app/src/program" } 1348 ] 1349 }) 1350 }; 1351 const sharedConfig: File = { 1352 path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`, 1353 content: JSON.stringify({ 1354 compilerOptions: { 1355 composite: true, 1356 outDir: "../../bld/library" 1357 } 1358 }) 1359 }; 1360 const sharedIndex: File = { 1361 path: `${tscWatch.projectRoot}/shared/src/library/index.ts`, 1362 content: `export function foo() {}` 1363 }; 1364 const sharedPackage: File = { 1365 path: `${tscWatch.projectRoot}/shared/package.json`, 1366 content: JSON.stringify({ 1367 name: "shared", 1368 version: "1.0.0", 1369 main: "bld/library/index.js", 1370 types: "bld/library/index.d.ts" 1371 }) 1372 }; 1373 const appConfig: File = { 1374 path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`, 1375 content: JSON.stringify({ 1376 compilerOptions: { 1377 composite: true, 1378 outDir: "../../bld/program", 1379 disableSourceOfProjectReferenceRedirect 1380 }, 1381 references: [ 1382 { path: "../../../shared/src/library" } 1383 ] 1384 }) 1385 }; 1386 const appBar: File = { 1387 path: `${tscWatch.projectRoot}/app/src/program/bar.ts`, 1388 content: `import {foo} from "shared";` 1389 }; 1390 const appIndex: File = { 1391 path: `${tscWatch.projectRoot}/app/src/program/index.ts`, 1392 content: `foo` 1393 }; 1394 const sharedSymlink: SymLink = { 1395 path: `${tscWatch.projectRoot}/node_modules/shared`, 1396 symLink: `${tscWatch.projectRoot}/shared` 1397 }; 1398 const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile]; 1399 const host = createServerHost(files); 1400 if (built) { 1401 tscWatch.solutionBuildWithBaseline(host, [solnConfig.path]); 1402 host.clearOutput(); 1403 } 1404 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 1405 openFilesForSession([appIndex], session); 1406 session.executeCommandSeq<protocol.CodeFixRequest>({ 1407 command: protocol.CommandTypes.GetCodeFixes, 1408 arguments: { 1409 file: appIndex.path, 1410 startLine: 1, 1411 startOffset: 1, 1412 endLine: 1, 1413 endOffset: 4, 1414 errorCodes: [Diagnostics.Cannot_find_name_0.code], 1415 } 1416 }); 1417 baselineTsserverLogs("projectReferences", `auto import with referenced project${built ? " when built" : ""}${disableSourceOfProjectReferenceRedirect ? " with disableSourceOfProjectReferenceRedirect": ""}`, session); 1418 } 1419 1420 it("when project is built", () => { 1421 verifyAutoImport(/*built*/ true); 1422 }); 1423 it("when project is not built", () => { 1424 verifyAutoImport(/*built*/ false); 1425 }); 1426 it("when disableSourceOfProjectReferenceRedirect is true", () => { 1427 verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); 1428 }); 1429 }); 1430 1431 it("when files from two projects are open and one project references", () => { 1432 function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] { 1433 const file: File = { 1434 path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`, 1435 content: `export const ${packageName}Const = 10;` 1436 }; 1437 const config: File = { 1438 path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`, 1439 content: JSON.stringify({ 1440 compilerOptions: { composite: true, ...optionsToExtend || {} }, 1441 references: references?.map(path => ({ path: `../${path}` })) 1442 }) 1443 }; 1444 return [file, config]; 1445 } 1446 const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]); 1447 const [coreFile, coreConfig] = getPackageAndFile("core"); 1448 const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1"); 1449 const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]); 1450 const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]); 1451 const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true }); 1452 const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]); 1453 const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true }); 1454 const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]); 1455 const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]); 1456 const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]); 1457 const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2"); 1458 1459 const host = createServerHost([ 1460 libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config, 1461 indirectFile, indirectConfig, coreRef1File, coreRef1Config, 1462 indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config, 1463 indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config, 1464 refToCoreRef3File, refToCoreRef3Config, 1465 indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config 1466 ], { useCaseSensitiveFileNames: true }); 1467 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 1468 openFilesForSession([mainFile, coreFile], session); 1469 1470 // Find all refs in coreFile 1471 session.executeCommandSeq<protocol.ReferencesRequest>({ 1472 command: protocol.CommandTypes.References, 1473 arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`) 1474 }); 1475 baselineTsserverLogs("projectReferences", `when files from two projects are open and one project references`, session); 1476 }); 1477 1478 describe("find refs to decl in other proj", () => { 1479 const indexA: File = { 1480 path: `${tscWatch.projectRoot}/a/index.ts`, 1481 content: `import { B } from "../b/lib"; 1482 1483const b: B = new B();` 1484 }; 1485 1486 const configB: File = { 1487 path: `${tscWatch.projectRoot}/b/tsconfig.json`, 1488 content: `{ 1489"compilerOptions": { 1490 "declarationMap": true, 1491 "outDir": "lib", 1492 "composite": true 1493} 1494}` 1495 }; 1496 1497 const indexB: File = { 1498 path: `${tscWatch.projectRoot}/b/index.ts`, 1499 content: `export class B { 1500 M() {} 1501}` 1502 }; 1503 1504 const helperB: File = { 1505 path: `${tscWatch.projectRoot}/b/helper.ts`, 1506 content: `import { B } from "."; 1507 1508const b: B = new B();` 1509 }; 1510 1511 const dtsB: File = { 1512 path: `${tscWatch.projectRoot}/b/lib/index.d.ts`, 1513 content: `export declare class B { 1514 M(): void; 1515} 1516//# sourceMappingURL=index.d.ts.map` 1517 }; 1518 1519 const dtsMapB: File = { 1520 path: `${tscWatch.projectRoot}/b/lib/index.d.ts.map`, 1521 content: `{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,qBAAa,CAAC;IACV,CAAC;CACJ"}` 1522 }; 1523 1524 function baselineDisableReferencedProjectLoad( 1525 projectAlreadyLoaded: boolean, 1526 disableReferencedProjectLoad: boolean, 1527 disableSourceOfProjectReferenceRedirect: boolean, 1528 dtsMapPresent: boolean) { 1529 1530 // Mangled to stay under windows path length limit 1531 const subScenario = 1532 `when proj ${projectAlreadyLoaded ? "is" : "is not"} loaded` + 1533 ` and refd proj loading is ${disableReferencedProjectLoad ? "disabled" : "enabled"}` + 1534 ` and proj ref redirects are ${disableSourceOfProjectReferenceRedirect ? "disabled" : "enabled"}` + 1535 ` and a decl map is ${dtsMapPresent ? "present" : "missing"}`; 1536 const compilerOptions: CompilerOptions = { 1537 disableReferencedProjectLoad, 1538 disableSourceOfProjectReferenceRedirect, 1539 composite: true 1540 }; 1541 1542 it(subScenario, () => { 1543 const configA: File = { 1544 path: `${tscWatch.projectRoot}/a/tsconfig.json`, 1545 content: `{ 1546 "compilerOptions": ${JSON.stringify(compilerOptions)}, 1547 "references": [{ "path": "../b" }] 1548 }` 1549 }; 1550 1551 const host = createServerHost([configA, indexA, configB, indexB, helperB, dtsB, ...(dtsMapPresent ? [dtsMapB] : [])]); 1552 const session = createSession(host, { logger: createLoggerWithInMemoryLogs(host) }); 1553 openFilesForSession([indexA, ...(projectAlreadyLoaded ? [helperB] : [])], session); 1554 1555 session.executeCommandSeq<protocol.ReferencesRequest>({ 1556 command: protocol.CommandTypes.References, 1557 arguments: protocolFileLocationFromSubstring(indexA, `B`, { index: 1 }) 1558 }); 1559 baselineTsserverLogs("projectReferences", `find refs to decl in other proj ${subScenario}`, session); 1560 }); 1561 } 1562 1563 /* eslint-disable local/boolean-trivia */ 1564 1565 // Pre-loaded = A file from project B is already open when FAR is invoked 1566 // dRPL = Project A has disableReferencedProjectLoad 1567 // dSOPRR = Project A has disableSourceOfProjectReferenceRedirect 1568 // Map = The declaration map file b/lib/index.d.ts.map exists 1569 // B refs = files under directory b in which references are found (all scenarios find all references in a/index.ts) 1570 1571 // Pre-loaded | dRPL | dSOPRR | Map | B state | Notes | B refs | Notes 1572 // -----------+--------+--------+----------+------------+--------------+---------------------+--------------------------------------------------- 1573 baselineDisableReferencedProjectLoad(true, true, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project 1574 baselineDisableReferencedProjectLoad(true, true, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded 1575 baselineDisableReferencedProjectLoad(true, true, false, true); // Pre-loaded | | index.ts, helper.ts | 1576 baselineDisableReferencedProjectLoad(true, true, false, false); // Pre-loaded | | index.ts, helper.ts | 1577 baselineDisableReferencedProjectLoad(true, false, true, true); // Pre-loaded | | index.ts, helper.ts | Via map and pre-loaded project 1578 baselineDisableReferencedProjectLoad(true, false, true, false); // Pre-loaded | | lib/index.d.ts | Even though project is loaded 1579 baselineDisableReferencedProjectLoad(true, false, false, true); // Pre-loaded | | index.ts, helper.ts | 1580 baselineDisableReferencedProjectLoad(true, false, false, false); // Pre-loaded | | index.ts, helper.ts | 1581 baselineDisableReferencedProjectLoad(false, true, true, true); // Not loaded | | lib/index.d.ts | Even though map is present 1582 baselineDisableReferencedProjectLoad(false, true, true, false); // Not loaded | | lib/index.d.ts | 1583 baselineDisableReferencedProjectLoad(false, true, false, true); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a 1584 baselineDisableReferencedProjectLoad(false, true, false, false); // Not loaded | | index.ts | But not helper.ts, which is not referenced from a 1585 baselineDisableReferencedProjectLoad(false, false, true, true); // Loaded | Via map | index.ts, helper.ts | Via map and newly loaded project 1586 baselineDisableReferencedProjectLoad(false, false, true, false); // Not loaded | | lib/index.d.ts | 1587 baselineDisableReferencedProjectLoad(false, false, false, true); // Loaded | Via redirect | index.ts, helper.ts | 1588 baselineDisableReferencedProjectLoad(false, false, false, false); // Loaded | Via redirect | index.ts, helper.ts | 1589 1590 /* eslint-enable local/boolean-trivia */ 1591 }); 1592 }); 1593} 1594