1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: with project references and tsbuild", () => { 3 function createHost(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { 4 const host = createServerHost(files); 5 // ts build should succeed 6 tscWatch.ensureErrorFreeBuild(host, rootNames); 7 return host; 8 } 9 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 = createHost(files, [containerConfig.path]); 27 28 // Open external project for the folder 29 const session = createSession(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 checkNumberOfProjects(service, { configuredProjects: 4 }); 37 files.forEach(f => { 38 const args: protocol.FileRequestArgs = { 39 file: f.path, 40 projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined 41 }; 42 const syntaxDiagnostics = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({ 43 command: protocol.CommandTypes.SyntacticDiagnosticsSync, 44 arguments: args 45 }).response; 46 assert.deepEqual(syntaxDiagnostics, []); 47 const semanticDiagnostics = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({ 48 command: protocol.CommandTypes.SemanticDiagnosticsSync, 49 arguments: args 50 }).response; 51 assert.deepEqual(semanticDiagnostics, []); 52 }); 53 const containerProject = service.configuredProjects.get(containerConfig.path)!; 54 checkProjectActualFiles(containerProject, [containerConfig.path]); 55 const optionsDiagnostics = session.executeCommandSeq<protocol.CompilerOptionsDiagnosticsRequest>({ 56 command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, 57 arguments: { projectFileName: containerProject.projectName } 58 }).response; 59 assert.deepEqual(optionsDiagnostics, []); 60 }); 61 62 it("can successfully find references with --out options", () => { 63 const host = createHost(files, [containerConfig.path]); 64 const session = createSession(host); 65 openFilesForSession([containerCompositeExec[1]], session); 66 const service = session.getProjectService(); 67 checkNumberOfProjects(service, { configuredProjects: 2 }); // compositeExec and solution 68 const solutionProject = service.configuredProjects.get(containerConfig.path)!; 69 assert.isTrue(solutionProject.isInitialLoadPending()); 70 const { file: myConstFile, start: myConstStart, end: myConstEnd } = protocolFileSpanFromSubstring({ 71 file: containerCompositeExec[1], 72 text: "myConst", 73 }); 74 const response = session.executeCommandSeq<protocol.RenameRequest>({ 75 command: protocol.CommandTypes.Rename, 76 arguments: { file: myConstFile, ...myConstStart } 77 }).response as protocol.RenameResponseBody; 78 79 const locationOfMyConstInLib = protocolFileSpanWithContextFromSubstring({ 80 file: containerLib[1], 81 text: "myConst", 82 contextText: "export const myConst = 30;" 83 }); 84 const { file: _, ...renameTextOfMyConstInLib } = locationOfMyConstInLib; 85 const locationOfMyConstInExec = protocolFileSpanWithContextFromSubstring({ 86 file: containerExec[1], 87 text: "myConst" 88 }); 89 const { file: myConstInExecFile, ...renameTextOfMyConstInExec } = locationOfMyConstInExec; 90 assert.deepEqual(response.locs, [ 91 { file: locationOfMyConstInLib.file, locs: [renameTextOfMyConstInLib] }, 92 { file: myConstFile, locs: [{ start: myConstStart, end: myConstEnd }] }, 93 { file: myConstInExecFile, locs: [renameTextOfMyConstInExec] }, 94 ]); 95 checkNumberOfProjects(service, { configuredProjects: 4 }); 96 assert.isFalse(solutionProject.isInitialLoadPending()); 97 }); 98 99 it("ancestor and project ref management", () => { 100 const tempFile: File = { 101 path: `/user/username/projects/temp/temp.ts`, 102 content: "let x = 10" 103 }; 104 const host = createHost(files.concat([tempFile]), [containerConfig.path]); 105 const session = createSession(host); 106 openFilesForSession([containerCompositeExec[1]], session); 107 const service = session.getProjectService(); 108 checkNumberOfProjects(service, { configuredProjects: 2 }); // compositeExec and solution 109 const solutionProject = service.configuredProjects.get(containerConfig.path)!; 110 assert.isTrue(solutionProject.isInitialLoadPending()); 111 112 // Open temp file and verify all projects alive 113 openFilesForSession([tempFile], session); 114 checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 }); 115 assert.isTrue(solutionProject.isInitialLoadPending()); 116 117 const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); 118 session.executeCommandSeq<protocol.RenameRequest>({ 119 command: protocol.CommandTypes.Rename, 120 arguments: { 121 file: containerCompositeExec[1].path, 122 ...locationOfMyConst 123 } 124 }); 125 126 // Ref projects are loaded 127 checkNumberOfProjects(service, { configuredProjects: 4, inferredProjects: 1 }); 128 assert.isFalse(solutionProject.isInitialLoadPending()); 129 130 // Open temp file and verify all projects alive 131 service.closeClientFile(tempFile.path); 132 openFilesForSession([tempFile], session); 133 checkNumberOfProjects(service, { configuredProjects: 4, inferredProjects: 1 }); 134 135 // Close all files and open temp file, only inferred project should be alive 136 service.closeClientFile(containerCompositeExec[1].path); 137 service.closeClientFile(tempFile.path); 138 openFilesForSession([tempFile], session); 139 checkNumberOfProjects(service, { inferredProjects: 1 }); 140 }); 141 }); 142 143 describe("with main and depedency project", () => { 144 const dependecyLocation = `${tscWatch.projectRoot}/dependency`; 145 const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; 146 const mainLocation = `${tscWatch.projectRoot}/main`; 147 const dependencyTs: File = { 148 path: `${dependecyLocation}/FnS.ts`, 149 content: `export function fn1() { } 150export function fn2() { } 151export function fn3() { } 152export function fn4() { } 153export function fn5() { } 154` 155 }; 156 const dependencyTsPath = dependencyTs.path.toLowerCase(); 157 const dependencyConfig: File = { 158 path: `${dependecyLocation}/tsconfig.json`, 159 content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) 160 }; 161 162 const mainTs: File = { 163 path: `${mainLocation}/main.ts`, 164 content: `import { 165 fn1, 166 fn2, 167 fn3, 168 fn4, 169 fn5 170} from '../decls/fns' 171 172fn1(); 173fn2(); 174fn3(); 175fn4(); 176fn5(); 177` 178 }; 179 const mainConfig: File = { 180 path: `${mainLocation}/tsconfig.json`, 181 content: JSON.stringify({ 182 compilerOptions: { composite: true, declarationMap: true }, 183 references: [{ path: "../dependency" }] 184 }) 185 }; 186 187 const randomFile: File = { 188 path: `${tscWatch.projectRoot}/random/random.ts`, 189 content: "let a = 10;" 190 }; 191 const randomConfig: File = { 192 path: `${tscWatch.projectRoot}/random/tsconfig.json`, 193 content: "{}" 194 }; 195 const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; 196 const dtsPath = dtsLocation.toLowerCase() as Path; 197 const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; 198 const dtsMapPath = dtsMapLocation.toLowerCase() as Path; 199 200 const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; 201 202 function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo: string) { 203 checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); 204 checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); 205 } 206 207 function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], reqName: string) { 208 verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path), reqName); 209 } 210 211 function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { 212 verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); 213 } 214 215 function declarationSpan(fn: number): protocol.TextSpanWithContext { 216 return { 217 start: { line: fn, offset: 17 }, 218 end: { line: fn, offset: 20 }, 219 contextStart: { line: fn, offset: 1 }, 220 contextEnd: { line: fn, offset: 26 } 221 }; 222 } 223 function importSpan(fn: number): protocol.TextSpanWithContext { 224 return { 225 start: { line: fn + 1, offset: 5 }, 226 end: { line: fn + 1, offset: 8 }, 227 contextStart: { line: 1, offset: 1 }, 228 contextEnd: { line: 7, offset: 22 } 229 }; 230 } 231 function usageSpan(fn: number): protocol.TextSpan { 232 return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; 233 } 234 235 function goToDefFromMainTs(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> { 236 const textSpan = usageSpan(fn); 237 const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; 238 return { 239 reqName: "goToDef", 240 request: { 241 command: protocol.CommandTypes.DefinitionAndBoundSpan, 242 arguments: { file: mainTs.path, ...textSpan.start } 243 }, 244 expectedResponse: { 245 // To dependency 246 definitions: [definition], 247 textSpan 248 } 249 }; 250 } 251 252 function goToDefFromMainTsWithNoMap(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> { 253 const textSpan = usageSpan(fn); 254 const definition = declarationSpan(fn); 255 const declareSpaceLength = "declare ".length; 256 return { 257 reqName: "goToDef", 258 request: { 259 command: protocol.CommandTypes.DefinitionAndBoundSpan, 260 arguments: { file: mainTs.path, ...textSpan.start } 261 }, 262 expectedResponse: { 263 // To the dts 264 definitions: [{ 265 file: dtsPath, 266 start: { line: fn, offset: definition.start.offset + declareSpaceLength }, 267 end: { line: fn, offset: definition.end.offset + declareSpaceLength }, 268 contextStart: { line: fn, offset: 1 }, 269 contextEnd: { line: fn, offset: 37 } 270 }], 271 textSpan 272 } 273 }; 274 } 275 276 function goToDefFromMainTsWithNoDts(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> { 277 const textSpan = usageSpan(fn); 278 return { 279 reqName: "goToDef", 280 request: { 281 command: protocol.CommandTypes.DefinitionAndBoundSpan, 282 arguments: { file: mainTs.path, ...textSpan.start } 283 }, 284 expectedResponse: { 285 // To import declaration 286 definitions: [{ file: mainTs.path, ...importSpan(fn) }], 287 textSpan 288 } 289 }; 290 } 291 292 function goToDefFromMainTsWithDependencyChange(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> { 293 const textSpan = usageSpan(fn); 294 return { 295 reqName: "goToDef", 296 request: { 297 command: protocol.CommandTypes.DefinitionAndBoundSpan, 298 arguments: { file: mainTs.path, ...textSpan.start } 299 }, 300 expectedResponse: { 301 // Definition on fn + 1 line 302 definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], 303 textSpan 304 } 305 }; 306 } 307 308 function goToDefFromMainTsProjectInfoVerifier(withRefs: boolean): ProjectInfoVerifier { 309 return { 310 openFile: mainTs, 311 openFileLastLine: 14, 312 configFile: mainConfig, 313 expectedProjectActualFiles: withRefs ? 314 [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : 315 [mainTs.path, libFile.path, mainConfig.path, dtsPath] 316 }; 317 } 318 319 function renameFromDependencyTs(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> { 320 const defSpan = declarationSpan(fn); 321 const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; 322 return { 323 reqName: "rename", 324 request: { 325 command: protocol.CommandTypes.Rename, 326 arguments: { file: dependencyTs.path, ...triggerSpan.start } 327 }, 328 expectedResponse: { 329 info: { 330 canRename: true, 331 fileToRename: undefined, 332 displayName: `fn${fn}`, 333 fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, 334 kind: ScriptElementKind.functionElement, 335 kindModifiers: "export", 336 triggerSpan 337 }, 338 locs: [ 339 { file: dependencyTs.path, locs: [defSpan] } 340 ] 341 } 342 }; 343 } 344 345 function renameFromDependencyTsWithDependencyChange(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> { 346 const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); 347 348 return { 349 ...rest, 350 expectedResponse: { 351 info: { 352 ...info as protocol.RenameInfoSuccess, 353 displayName: `fn${fn}`, 354 fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, 355 }, 356 locs 357 } 358 }; 359 } 360 361 function renameFromDependencyTsProjectInfoVerifier(): ProjectInfoVerifier { 362 return { 363 openFile: dependencyTs, 364 openFileLastLine: 6, 365 configFile: dependencyConfig, 366 expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path] 367 }; 368 } 369 370 function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> { 371 const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); 372 const { info, locs } = expectedResponse; 373 return { 374 reqName, 375 request, 376 expectedResponse: { 377 info, 378 locs: [ 379 locs[0], 380 { 381 file: mainTs.path, 382 locs: [ 383 importSpan(fn), 384 usageSpan(fn) 385 ] 386 } 387 ] 388 } 389 }; 390 } 391 392 function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> { 393 const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); 394 const { info, locs } = expectedResponse; 395 return { 396 reqName, 397 request, 398 expectedResponse: { 399 info, 400 locs: [ 401 locs[0], 402 { 403 file: mainTs.path, 404 locs: [ 405 importSpan(fn), 406 usageSpan(fn) 407 ] 408 } 409 ] 410 } 411 }; 412 } 413 414 function removePath(array: readonly string[], ...delPaths: string[]) { 415 return array.filter(a => { 416 const aLower = a.toLowerCase(); 417 return delPaths.every(dPath => dPath !== aLower); 418 }); 419 } 420 421 interface Action<Req = protocol.Request, Response = {}> { 422 reqName: string; 423 request: Partial<Req>; 424 expectedResponse: Response; 425 } 426 interface ActionInfo<Req = protocol.Request, Response = {}> { 427 action: (fn: number) => Action<Req, Response>; 428 closedInfos: readonly string[]; 429 otherWatchedFiles: readonly string[]; 430 expectsDts: boolean; 431 expectsMap: boolean; 432 freshMapInfo?: boolean; 433 freshDocumentMapper?: boolean; 434 skipDtsMapCheck?: boolean; 435 } 436 type ActionKey = keyof ActionInfoVerifier; 437 type ActionInfoGetterFn<Req = protocol.Request, Response = {}> = () => ActionInfo<Req, Response>; 438 type ActionInfoSpreader<Req = protocol.Request, Response = {}> = [ 439 ActionKey, // Key to get initial value and pass this value to spread function 440 (actionInfo: ActionInfo<Req, Response>) => Partial<ActionInfo<Req, Response>> 441 ]; 442 type ActionInfoGetter<Req = protocol.Request, Response = {}> = ActionInfoGetterFn<Req, Response> | ActionKey | ActionInfoSpreader<Req, Response>; 443 interface ProjectInfoVerifier { 444 openFile: File; 445 openFileLastLine: number; 446 configFile: File; 447 expectedProjectActualFiles: readonly string[]; 448 } 449 interface ActionInfoVerifier<Req = protocol.Request, Response = {}> { 450 main: ActionInfoGetter<Req, Response>; 451 change: ActionInfoGetter<Req, Response>; 452 dtsChange: ActionInfoGetter<Req, Response>; 453 mapChange: ActionInfoGetter<Req, Response>; 454 noMap: ActionInfoGetter<Req, Response>; 455 mapFileCreated: ActionInfoGetter<Req, Response>; 456 mapFileDeleted: ActionInfoGetter<Req, Response>; 457 noDts: ActionInfoGetter<Req, Response>; 458 dtsFileCreated: ActionInfoGetter<Req, Response>; 459 dtsFileDeleted: ActionInfoGetter<Req, Response>; 460 dependencyChange: ActionInfoGetter<Req, Response>; 461 noBuild: ActionInfoGetter<Req, Response>; 462 } 463 interface DocumentPositionMapperVerifier<Req = protocol.Request, Response = {}> extends ProjectInfoVerifier, ActionInfoVerifier<Req, Response> { 464 } 465 466 interface VerifierAndWithRefs { 467 withRefs: boolean; 468 disableSourceOfProjectReferenceRedirect?: true; 469 verifier: (withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) => readonly DocumentPositionMapperVerifier[]; 470 } 471 472 function openFiles(verifiers: readonly DocumentPositionMapperVerifier[]) { 473 return verifiers.map(v => v.openFile); 474 } 475 interface OpenTsFile extends VerifierAndWithRefs { 476 onHostCreate?: (host: TestServerHost) => void; 477 } 478 function openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier, onHostCreate }: OpenTsFile) { 479 const host = createHost(files, [mainConfig.path]); 480 if (!withRefs) { 481 // Erase project reference 482 host.writeFile(mainConfig.path, JSON.stringify({ 483 compilerOptions: { composite: true, declarationMap: true } 484 })); 485 } 486 else if (disableSourceOfProjectReferenceRedirect) { 487 // Erase project reference 488 host.writeFile(mainConfig.path, JSON.stringify({ 489 compilerOptions: { 490 composite: true, 491 declarationMap: true, 492 disableSourceOfProjectReferenceRedirect: !!disableSourceOfProjectReferenceRedirect 493 }, 494 references: [{ path: "../dependency" }] 495 })); 496 } 497 if (onHostCreate) { 498 onHostCreate(host); 499 } 500 const session = createSession(host); 501 const verifiers = verifier(withRefs && !disableSourceOfProjectReferenceRedirect, disableSourceOfProjectReferenceRedirect); 502 openFilesForSession([...openFiles(verifiers), randomFile], session); 503 return { host, session, verifiers }; 504 } 505 506 function checkProject(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[], noDts?: true) { 507 const service = session.getProjectService(); 508 checkNumberOfProjects(service, { configuredProjects: 1 + verifiers.length }); 509 verifiers.forEach(({ configFile, expectedProjectActualFiles }) => { 510 checkProjectActualFiles( 511 service.configuredProjects.get(configFile.path.toLowerCase())!, 512 noDts ? 513 expectedProjectActualFiles.filter(f => f.toLowerCase() !== dtsPath) : 514 expectedProjectActualFiles 515 ); 516 }); 517 } 518 519 function firstAction(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) { 520 for (const { action } of getActionInfo(verifiers, "main")) { 521 const { request } = action(1); 522 session.executeCommandSeq(request); 523 } 524 } 525 526 function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { 527 const { response } = session.executeCommandSeq(request); 528 assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); 529 } 530 531 function verifyScriptInfoPresence(session: TestSession, path: string, expectedToBePresent: boolean, reqName: string) { 532 const info = session.getProjectService().filenameToScriptInfo.get(path); 533 if (expectedToBePresent) { 534 assert.isDefined(info, `${reqName}:: ${path} expected to be present`); 535 } 536 else { 537 assert.isUndefined(info, `${reqName}:: ${path} expected to be not present`); 538 } 539 return info; 540 } 541 542 interface VerifyDocumentPositionMapper { 543 session: TestSession; 544 dependencyMap: server.ScriptInfo | undefined; 545 documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; 546 equal: boolean; 547 debugInfo: string; 548 } 549 function verifyDocumentPositionMapper({ session, dependencyMap, documentPositionMapper, equal, debugInfo }: VerifyDocumentPositionMapper) { 550 assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, debugInfo); 551 if (dependencyMap) { 552 if (equal) { 553 assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo); 554 } 555 else { 556 assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo); 557 } 558 } 559 } 560 561 function getActionInfoOfVerfier(verifier: DocumentPositionMapperVerifier, actionKey: ActionKey): ActionInfo { 562 const actionInfoGetter = verifier[actionKey]; 563 if (isString(actionInfoGetter)) { 564 return getActionInfoOfVerfier(verifier, actionInfoGetter); 565 } 566 567 if (isArray(actionInfoGetter)) { 568 const initialValue = getActionInfoOfVerfier(verifier, actionInfoGetter[0]); 569 return { 570 ...initialValue, 571 ...actionInfoGetter[1](initialValue) 572 }; 573 } 574 575 return actionInfoGetter(); 576 } 577 578 function getActionInfo(verifiers: readonly DocumentPositionMapperVerifier[], actionKey: ActionKey): ActionInfo[] { 579 return verifiers.map(v => getActionInfoOfVerfier(v, actionKey)); 580 } 581 582 interface VerifyAllFnAction { 583 session: TestSession; 584 host: TestServerHost; 585 verifiers: readonly DocumentPositionMapperVerifier[]; 586 actionKey: ActionKey; 587 sourceMapPath?: server.ScriptInfo["sourceMapFilePath"]; 588 dependencyMap?: server.ScriptInfo | undefined; 589 documentPositionMapper?: server.ScriptInfo["documentPositionMapper"]; 590 } 591 interface VerifyAllFnActionResult { 592 actionInfos: readonly ActionInfo[]; 593 actionKey: ActionKey; 594 dependencyMap: server.ScriptInfo | undefined; 595 documentPositionMapper: server.ScriptInfo["documentPositionMapper"] | undefined; 596 } 597 function verifyAllFnAction({ 598 session, 599 host, 600 verifiers, 601 actionKey, 602 dependencyMap, 603 documentPositionMapper, 604 }: VerifyAllFnAction): VerifyAllFnActionResult { 605 const actionInfos = getActionInfo(verifiers, actionKey); 606 let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; 607 // action 608 let first = true; 609 for (const { 610 action, 611 closedInfos, 612 otherWatchedFiles, 613 expectsDts, 614 expectsMap, 615 freshMapInfo, 616 freshDocumentMapper, 617 skipDtsMapCheck 618 } of actionInfos) { 619 for (let fn = 1; fn <= 5; fn++) { 620 const fnAction = action(fn); 621 verifyAction(session, fnAction); 622 const debugInfo = `${actionKey}:: ${fnAction.reqName}:: ${fn}`; 623 const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, debugInfo); 624 const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, debugInfo); 625 verifyInfosWithRandom( 626 session, 627 host, 628 openFiles(verifiers).map(f => f.path), 629 closedInfos, 630 otherWatchedFiles, 631 debugInfo 632 ); 633 634 if (dtsInfo) { 635 if (first || (fn === 1 && freshMapInfo)) { 636 if (!skipDtsMapCheck) { 637 if (dtsMapInfo) { 638 assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, debugInfo); 639 } 640 else { 641 assert.isNotString(dtsInfo.sourceMapFilePath, debugInfo); 642 assert.isNotFalse(dtsInfo.sourceMapFilePath, debugInfo); 643 assert.isDefined(dtsInfo.sourceMapFilePath, debugInfo); 644 } 645 } 646 } 647 else { 648 assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, debugInfo); 649 } 650 } 651 652 if (!first && (fn !== 1 || !freshMapInfo)) { 653 verifyDocumentPositionMapper({ 654 session, 655 dependencyMap, 656 documentPositionMapper, 657 equal: fn !== 1 || !freshDocumentMapper, 658 debugInfo 659 }); 660 } 661 sourceMapPath = dtsInfo && dtsInfo.sourceMapFilePath; 662 dependencyMap = dtsMapInfo; 663 documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; 664 first = false; 665 } 666 } 667 668 return { actionInfos, actionKey, dependencyMap, documentPositionMapper }; 669 } 670 671 function verifyScriptInfoCollection( 672 session: TestSession, 673 host: TestServerHost, 674 verifiers: readonly DocumentPositionMapperVerifier[], 675 { dependencyMap, documentPositionMapper, actionInfos, actionKey }: VerifyAllFnActionResult 676 ) { 677 // Collecting at this point retains dependency.d.ts and map 678 closeFilesForSession([randomFile], session); 679 openFilesForSession([randomFile], session); 680 681 const { closedInfos, otherWatchedFiles } = last(actionInfos); 682 const debugInfo = `${actionKey} Collection`; 683 verifyInfosWithRandom( 684 session, 685 host, 686 openFiles(verifiers).map(f => f.path), 687 closedInfos, 688 otherWatchedFiles, 689 debugInfo 690 ); 691 verifyDocumentPositionMapper({ 692 session, 693 dependencyMap, 694 documentPositionMapper, 695 equal: true, 696 debugInfo 697 }); 698 699 // Closing open file, removes dependencies too 700 closeFilesForSession([...openFiles(verifiers), randomFile], session); 701 openFilesForSession([randomFile], session); 702 verifyOnlyRandomInfos(session, host); 703 } 704 705 function verifyScenarioAndScriptInfoCollection( 706 session: TestSession, 707 host: TestServerHost, 708 verifiers: readonly DocumentPositionMapperVerifier[], 709 actionKey: ActionKey, 710 noDts?: true 711 ) { 712 // Main scenario action 713 const result = verifyAllFnAction({ session, host, verifiers, actionKey }); 714 checkProject(session, verifiers, noDts); 715 verifyScriptInfoCollection(session, host, verifiers, result); 716 } 717 718 function verifyScenarioWithChangesWorker( 719 { 720 scenarioName, 721 verifier, 722 withRefs, 723 disableSourceOfProjectReferenceRedirect, 724 change, 725 afterChangeActionKey 726 }: VerifyScenarioWithChanges, 727 timeoutBeforeAction: boolean, 728 ) { 729 it(scenarioName, () => { 730 const { host, session, verifiers } = openTsFile({ verifier, withRefs, disableSourceOfProjectReferenceRedirect }); 731 732 // Create DocumentPositionMapper 733 firstAction(session, verifiers); 734 const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); 735 const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; 736 737 // change 738 change(host, session, verifiers); 739 if (timeoutBeforeAction) { 740 host.runQueuedTimeoutCallbacks(); 741 checkProject(session, verifiers); 742 verifyDocumentPositionMapper({ 743 session, 744 dependencyMap, 745 documentPositionMapper, 746 equal: true, 747 debugInfo: "After change timeout" 748 }); 749 } 750 751 // action 752 verifyAllFnAction({ 753 session, 754 host, 755 verifiers, 756 actionKey: afterChangeActionKey, 757 dependencyMap, 758 documentPositionMapper 759 }); 760 }); 761 } 762 763 interface VerifyScenarioWithChanges extends VerifierAndWithRefs { 764 scenarioName: string; 765 change: (host: TestServerHost, session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) => void; 766 afterChangeActionKey: ActionKey; 767 } 768 function verifyScenarioWithChanges(verify: VerifyScenarioWithChanges) { 769 describe("when timeout occurs before request", () => { 770 verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ true); 771 }); 772 773 describe("when timeout does not occur before request", () => { 774 verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ false); 775 }); 776 } 777 778 interface VerifyScenarioWhenFileNotPresent extends VerifierAndWithRefs { 779 scenarioName: string; 780 fileLocation: string; 781 fileNotPresentKey: ActionKey; 782 fileCreatedKey: ActionKey; 783 fileDeletedKey: ActionKey; 784 noDts?: true; 785 } 786 function verifyScenarioWhenFileNotPresent({ 787 scenarioName, 788 verifier, 789 withRefs, 790 disableSourceOfProjectReferenceRedirect, 791 fileLocation, 792 fileNotPresentKey, 793 fileCreatedKey, 794 fileDeletedKey, 795 noDts 796 }: VerifyScenarioWhenFileNotPresent) { 797 describe(scenarioName, () => { 798 it("when file is not present", () => { 799 const { host, session, verifiers } = openTsFile({ 800 verifier, 801 withRefs, 802 disableSourceOfProjectReferenceRedirect, 803 onHostCreate: host => host.deleteFile(fileLocation) 804 }); 805 checkProject(session, verifiers, noDts); 806 807 verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileNotPresentKey, noDts); 808 }); 809 810 it("when file is created after actions on projects", () => { 811 let fileContents: string | undefined; 812 const { host, session, verifiers } = openTsFile({ 813 verifier, 814 withRefs, 815 disableSourceOfProjectReferenceRedirect, 816 onHostCreate: host => { 817 fileContents = host.readFile(fileLocation); 818 host.deleteFile(fileLocation); 819 } 820 }); 821 firstAction(session, verifiers); 822 823 host.writeFile(fileLocation, fileContents!); 824 verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileCreatedKey); 825 }); 826 827 it("when file is deleted after actions on the projects", () => { 828 const { host, session, verifiers } = openTsFile({ verifier, disableSourceOfProjectReferenceRedirect, withRefs }); 829 firstAction(session, verifiers); 830 831 // The dependency file is deleted when orphan files are collected 832 host.deleteFile(fileLocation); 833 // Verify with deleted action key 834 verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey }); 835 checkProject(session, verifiers, noDts); 836 837 // Script info collection should behave as fileNotPresentKey 838 verifyScriptInfoCollection( 839 session, 840 host, 841 verifiers, 842 { 843 actionInfos: getActionInfo(verifiers, fileNotPresentKey), 844 actionKey: fileNotPresentKey, 845 dependencyMap: undefined, 846 documentPositionMapper: undefined 847 } 848 ); 849 }); 850 }); 851 } 852 853 function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) { 854 it(mainScenario, () => { 855 const { host, session, verifiers } = openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier }); 856 checkProject(session, verifiers); 857 verifyScenarioAndScriptInfoCollection(session, host, verifiers, "main"); 858 }); 859 860 // Edit 861 verifyScenarioWithChanges({ 862 scenarioName: "when usage file changes, document position mapper doesnt change", 863 verifier, 864 withRefs, 865 disableSourceOfProjectReferenceRedirect, 866 change: (_host, session, verifiers) => verifiers.forEach( 867 verifier => session.executeCommandSeq<protocol.ChangeRequest>({ 868 command: protocol.CommandTypes.Change, 869 arguments: { 870 file: verifier.openFile.path, 871 line: verifier.openFileLastLine, 872 offset: 1, 873 endLine: verifier.openFileLastLine, 874 endOffset: 1, 875 insertString: "const x = 10;" 876 } 877 }) 878 ), 879 afterChangeActionKey: "change" 880 }); 881 882 // Edit dts to add new fn 883 verifyScenarioWithChanges({ 884 scenarioName: "when dependency .d.ts changes, document position mapper doesnt change", 885 verifier, 886 withRefs, 887 disableSourceOfProjectReferenceRedirect, 888 change: host => host.writeFile( 889 dtsLocation, 890 host.readFile(dtsLocation)!.replace( 891 "//# sourceMappingURL=FnS.d.ts.map", 892 `export declare function fn6(): void; 893//# sourceMappingURL=FnS.d.ts.map` 894 ) 895 ), 896 afterChangeActionKey: "dtsChange" 897 }); 898 899 // Edit map file to represent added new line 900 verifyScenarioWithChanges({ 901 scenarioName: "when dependency file's map changes", 902 verifier, 903 withRefs, 904 disableSourceOfProjectReferenceRedirect, 905 change: host => host.writeFile( 906 dtsMapLocation, 907 `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` 908 ), 909 afterChangeActionKey: "mapChange" 910 }); 911 912 verifyScenarioWhenFileNotPresent({ 913 scenarioName: "with depedency files map file", 914 verifier, 915 withRefs, 916 disableSourceOfProjectReferenceRedirect, 917 fileLocation: dtsMapLocation, 918 fileNotPresentKey: "noMap", 919 fileCreatedKey: "mapFileCreated", 920 fileDeletedKey: "mapFileDeleted" 921 }); 922 923 verifyScenarioWhenFileNotPresent({ 924 scenarioName: "with depedency .d.ts file", 925 verifier, 926 withRefs, 927 disableSourceOfProjectReferenceRedirect, 928 fileLocation: dtsLocation, 929 fileNotPresentKey: "noDts", 930 fileCreatedKey: "dtsFileCreated", 931 fileDeletedKey: "dtsFileDeleted", 932 noDts: true 933 }); 934 935 if (withRefs && !disableSourceOfProjectReferenceRedirect) { 936 verifyScenarioWithChanges({ 937 scenarioName: "when defining project source changes", 938 verifier, 939 withRefs, 940 change: (host, session, verifiers) => { 941 // Make change, without rebuild of solution 942 if (contains(openFiles(verifiers), dependencyTs)) { 943 session.executeCommandSeq<protocol.ChangeRequest>({ 944 command: protocol.CommandTypes.Change, 945 arguments: { 946 file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } 947`} 948 }); 949 } 950 else { 951 host.writeFile(dependencyTs.path, `function fooBar() { } 952${dependencyTs.content}`); 953 } 954 }, 955 afterChangeActionKey: "dependencyChange" 956 }); 957 958 it("when projects are not built", () => { 959 const host = createServerHost(files); 960 const session = createSession(host); 961 const verifiers = verifier(withRefs); 962 openFilesForSession([...openFiles(verifiers), randomFile], session); 963 verifyScenarioAndScriptInfoCollection(session, host, verifiers, "noBuild"); 964 }); 965 } 966 } 967 968 interface VerifyScenario { 969 mainScenario: string; 970 verifier: (withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) => readonly DocumentPositionMapperVerifier[]; 971 } 972 function verifyScenario(scenario: VerifyScenario) { 973 describe(scenario.mainScenario, () => { 974 describe("when main tsconfig doesnt have project reference", () => { 975 verifyScenarioWorker(scenario, /*withRefs*/ false); 976 }); 977 describe("when main tsconfig has project reference", () => { 978 verifyScenarioWorker(scenario, /*withRefs*/ true); 979 }); 980 describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { 981 verifyScenarioWorker(scenario, /*withRefs*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); 982 }); 983 }); 984 } 985 986 describe("from project that uses dependency", () => { 987 verifyScenario({ 988 mainScenario: "can go to definition correctly", 989 verifier: (withRefs, disableSourceOfProjectReferenceRedirect) => [ 990 { 991 ...goToDefFromMainTsProjectInfoVerifier(withRefs), 992 main: () => ({ 993 action: goToDefFromMainTs, 994 closedInfos: withRefs ? 995 [dependencyTs.path, dependencyConfig.path, libFile.path] : 996 disableSourceOfProjectReferenceRedirect ? 997 [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : 998 [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation], 999 otherWatchedFiles: [mainConfig.path], 1000 expectsDts: !withRefs, // Dts script info present only if no project reference 1001 expectsMap: !withRefs // Map script info present only if no project reference 1002 }), 1003 change: "main", 1004 dtsChange: "main", 1005 mapChange: ["main", () => ({ 1006 freshDocumentMapper: true 1007 })], 1008 noMap: withRefs ? 1009 "main" : 1010 ["main", main => ({ 1011 action: goToDefFromMainTsWithNoMap, 1012 // Because map is deleted, dts and dependency are released 1013 closedInfos: removePath(main.closedInfos, dtsMapPath, dependencyTsPath), 1014 // Watches deleted file 1015 otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), 1016 expectsMap: false 1017 })], 1018 mapFileCreated: "main", 1019 mapFileDeleted: withRefs ? 1020 "main" : 1021 ["noMap", noMap => ({ 1022 // The script info for depedency is collected only after file open 1023 closedInfos: noMap.closedInfos.concat(dependencyTs.path) 1024 })], 1025 noDts: withRefs ? 1026 "main" : 1027 ["main", main => ({ 1028 action: goToDefFromMainTsWithNoDts, 1029 // No dts, no map, no dependency 1030 closedInfos: removePath(main.closedInfos, dtsPath, dtsMapPath, dependencyTsPath), 1031 expectsDts: false, 1032 expectsMap: false 1033 })], 1034 dtsFileCreated: "main", 1035 dtsFileDeleted: withRefs ? 1036 "main" : 1037 ["noDts", noDts => ({ 1038 // The script info for map is collected only after file open 1039 closedInfos: noDts.closedInfos.concat(dependencyTs.path, dtsMapLocation), 1040 expectsMap: true 1041 })], 1042 dependencyChange: ["main", () => ({ 1043 action: goToDefFromMainTsWithDependencyChange, 1044 })], 1045 noBuild: "noDts" 1046 } 1047 ] 1048 }); 1049 }); 1050 1051 describe("from defining project", () => { 1052 verifyScenario({ 1053 mainScenario: "rename locations from dependency", 1054 verifier: () => [ 1055 { 1056 ...renameFromDependencyTsProjectInfoVerifier(), 1057 main: () => ({ 1058 action: renameFromDependencyTs, 1059 closedInfos: [libFile.path, dtsLocation, dtsMapLocation], 1060 otherWatchedFiles: [dependencyConfig.path], 1061 expectsDts: true, 1062 expectsMap: true 1063 }), 1064 change: "main", 1065 dtsChange: "main", 1066 mapChange: ["main", () => ({ 1067 freshDocumentMapper: true 1068 })], 1069 noMap: ["main", main => ({ 1070 // No map 1071 closedInfos: removePath(main.closedInfos, dtsMapPath), 1072 // watch map 1073 otherWatchedFiles: [...main.otherWatchedFiles, dtsMapLocation], 1074 expectsMap: false 1075 })], 1076 mapFileCreated: "main", 1077 mapFileDeleted: "noMap", 1078 noDts: ["main", main => ({ 1079 // no dts or map since dts itself doesnt exist 1080 closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath), 1081 // watch deleted file 1082 otherWatchedFiles: [...main.otherWatchedFiles, dtsLocation], 1083 expectsDts: false, 1084 expectsMap: false 1085 })], 1086 dtsFileCreated: "main", 1087 dtsFileDeleted: ["noDts", noDts => ({ 1088 // Map is collected after file open 1089 closedInfos: noDts.closedInfos.concat(dtsMapLocation), 1090 expectsMap: true 1091 })], 1092 dependencyChange: ["main", () => ({ 1093 action: renameFromDependencyTsWithDependencyChange 1094 })], 1095 noBuild: "noDts" 1096 } 1097 ] 1098 }); 1099 }); 1100 1101 describe("when opening depedency and usage project", () => { 1102 verifyScenario({ 1103 mainScenario: "goto Definition in usage and rename locations from defining project", 1104 verifier: (withRefs, disableSourceOfProjectReferenceRedirect) => [ 1105 { 1106 ...goToDefFromMainTsProjectInfoVerifier(withRefs), 1107 main: () => ({ 1108 action: goToDefFromMainTs, 1109 // DependencyTs is open, so omit it from closed infos 1110 closedInfos: withRefs ? 1111 [dependencyConfig.path, libFile.path] : 1112 disableSourceOfProjectReferenceRedirect ? 1113 [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : 1114 [libFile.path, dtsPath, dtsMapLocation], 1115 otherWatchedFiles: withRefs || disableSourceOfProjectReferenceRedirect ? 1116 [mainConfig.path] : // dependencyConfig is in closed info 1117 [mainConfig.path, dependencyConfig.path], 1118 expectsDts: !withRefs, // Dts script info present only if no project reference 1119 expectsMap: !withRefs // Map script info present only if no project reference 1120 }), 1121 change: withRefs ? 1122 ["main", main => ({ 1123 // Because before this rename is done the closed info remains same as rename's main operation 1124 closedInfos: main.closedInfos.concat(dtsLocation, dtsMapLocation), 1125 expectsDts: true, 1126 expectsMap: true 1127 })] : 1128 "main", 1129 dtsChange: "change", 1130 mapChange: "change", 1131 noMap: withRefs ? 1132 "main" : 1133 ["main", main => ({ 1134 action: goToDefFromMainTsWithNoMap, 1135 closedInfos: removePath(main.closedInfos, dtsMapPath), 1136 otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), 1137 expectsMap: false 1138 })], 1139 mapFileCreated: withRefs ? 1140 ["main", main => ({ 1141 // Because before this rename is done the closed info remains same as rename's main 1142 closedInfos: main.closedInfos.concat(dtsLocation), 1143 expectsDts: true, 1144 // This operation doesnt need map so the map info path in dts is not refreshed 1145 skipDtsMapCheck: withRefs 1146 })] : 1147 "main", 1148 mapFileDeleted: withRefs ? 1149 ["noMap", noMap => ({ 1150 // Because before this rename is done the closed info remains same as rename's noMap operation 1151 closedInfos: noMap.closedInfos.concat(dtsLocation), 1152 expectsDts: true, 1153 // This operation doesnt need map so the map info path in dts is not refreshed 1154 skipDtsMapCheck: true 1155 })] : 1156 "noMap", 1157 noDts: withRefs ? 1158 "main" : 1159 ["main", main => ({ 1160 action: goToDefFromMainTsWithNoDts, 1161 closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath), 1162 expectsDts: false, 1163 expectsMap: false 1164 })], 1165 dtsFileCreated: withRefs ? 1166 ["main", main => ({ 1167 // Since the project for dependency is not updated, the watcher from rename for dts still there 1168 otherWatchedFiles: main.otherWatchedFiles.concat(dtsLocation) 1169 })] : 1170 "main", 1171 dtsFileDeleted: ["noDts", noDts => ({ 1172 // Map collection after file open 1173 closedInfos: noDts.closedInfos.concat(dtsMapLocation), 1174 expectsMap: true 1175 })], 1176 dependencyChange: ["change", () => ({ 1177 action: goToDefFromMainTsWithDependencyChange, 1178 })], 1179 noBuild: "noDts" 1180 }, 1181 { 1182 ...renameFromDependencyTsProjectInfoVerifier(), 1183 main: () => ({ 1184 action: renameFromDependencyTsWithBothProjectsOpen, 1185 // DependencyTs is open, so omit it from closed infos 1186 closedInfos: withRefs ? 1187 [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : 1188 disableSourceOfProjectReferenceRedirect ? 1189 [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : 1190 [libFile.path, dtsPath, dtsMapLocation], 1191 otherWatchedFiles: withRefs || disableSourceOfProjectReferenceRedirect ? 1192 [mainConfig.path] : // dependencyConfig is in closed info 1193 [mainConfig.path, dependencyConfig.path], 1194 expectsDts: true, 1195 expectsMap: true, 1196 freshMapInfo: withRefs 1197 }), 1198 change: ["main", () => ({ 1199 freshMapInfo: false 1200 })], 1201 dtsChange: "change", 1202 mapChange: ["main", () => ({ 1203 freshMapInfo: false, 1204 freshDocumentMapper: withRefs 1205 })], 1206 noMap: ["main", main => ({ 1207 action: withRefs ? 1208 renameFromDependencyTsWithBothProjectsOpen : 1209 renameFromDependencyTs, 1210 closedInfos: removePath(main.closedInfos, dtsMapPath), 1211 otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), 1212 expectsMap: false, 1213 freshDocumentMapper: withRefs 1214 })], 1215 mapFileCreated: "main", 1216 mapFileDeleted: "noMap", 1217 noDts: ["change", change => ({ 1218 action: withRefs ? 1219 renameFromDependencyTsWithBothProjectsOpen : 1220 renameFromDependencyTs, 1221 closedInfos: removePath(change.closedInfos, dtsPath, dtsMapPath), 1222 otherWatchedFiles: change.otherWatchedFiles.concat(dtsLocation), 1223 expectsDts: false, 1224 expectsMap: false 1225 })], 1226 dtsFileCreated: "main", 1227 dtsFileDeleted: ["noDts", noDts => ({ 1228 // Map collection after file open 1229 closedInfos: noDts.closedInfos.concat(dtsMapLocation), 1230 expectsMap: true 1231 })], 1232 dependencyChange: ["change", () => ({ 1233 action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange 1234 })], 1235 noBuild: "noDts" 1236 } 1237 ] 1238 }); 1239 }); 1240 }); 1241 1242 describe("when root file is file from referenced project", () => { 1243 function verify(disableSourceOfProjectReferenceRedirect: boolean) { 1244 const projectLocation = `/user/username/projects/project`; 1245 const commonConfig: File = { 1246 path: `${projectLocation}/src/common/tsconfig.json`, 1247 content: JSON.stringify({ 1248 compilerOptions: { 1249 composite: true, 1250 declarationMap: true, 1251 outDir: "../../out", 1252 baseUrl: "..", 1253 disableSourceOfProjectReferenceRedirect 1254 }, 1255 include: ["./**/*"] 1256 }) 1257 }; 1258 const keyboardTs: File = { 1259 path: `${projectLocation}/src/common/input/keyboard.ts`, 1260 content: `function bar() { return "just a random function so .d.ts location doesnt match"; } 1261export function evaluateKeyboardEvent() { }` 1262 }; 1263 const keyboardTestTs: File = { 1264 path: `${projectLocation}/src/common/input/keyboard.test.ts`, 1265 content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; 1266function testEvaluateKeyboardEvent() { 1267 return evaluateKeyboardEvent(); 1268} 1269` 1270 }; 1271 const srcConfig: File = { 1272 path: `${projectLocation}/src/tsconfig.json`, 1273 content: JSON.stringify({ 1274 compilerOptions: { 1275 composite: true, 1276 declarationMap: true, 1277 outDir: "../out", 1278 baseUrl: ".", 1279 paths: { 1280 "common/*": ["./common/*"], 1281 }, 1282 tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo", 1283 disableSourceOfProjectReferenceRedirect 1284 }, 1285 include: ["./**/*"], 1286 references: [ 1287 { path: "./common" } 1288 ] 1289 }) 1290 }; 1291 const terminalTs: File = { 1292 path: `${projectLocation}/src/terminal.ts`, 1293 content: `import { evaluateKeyboardEvent } from 'common/input/keyboard'; 1294function foo() { 1295 return evaluateKeyboardEvent(); 1296} 1297` 1298 }; 1299 const host = createHost( 1300 [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], 1301 [srcConfig.path] 1302 ); 1303 const session = createSession(host); 1304 openFilesForSession([keyboardTs, terminalTs], session); 1305 1306 const searchStr = "evaluateKeyboardEvent"; 1307 const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`; 1308 const result = session.executeCommandSeq<protocol.ReferencesRequest>({ 1309 command: protocol.CommandTypes.References, 1310 arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr) 1311 }).response as protocol.ReferencesResponseBody; 1312 assert.deepEqual(result, { 1313 refs: [ 1314 makeReferenceItem({ 1315 file: keyboardTs, 1316 text: searchStr, 1317 contextText: `export function evaluateKeyboardEvent() { }`, 1318 isDefinition: true, 1319 lineText: `export function evaluateKeyboardEvent() { }` 1320 }), 1321 makeReferenceItem({ 1322 file: keyboardTestTs, 1323 text: searchStr, 1324 contextText: importStr, 1325 isDefinition: true, 1326 lineText: importStr 1327 }), 1328 makeReferenceItem({ 1329 file: keyboardTestTs, 1330 text: searchStr, 1331 options: { index: 1 }, 1332 isDefinition: false, 1333 lineText: ` return evaluateKeyboardEvent();` 1334 }), 1335 makeReferenceItem({ 1336 file: terminalTs, 1337 text: searchStr, 1338 contextText: importStr, 1339 isDefinition: true, 1340 lineText: importStr 1341 }), 1342 makeReferenceItem({ 1343 file: terminalTs, 1344 text: searchStr, 1345 options: { index: 1 }, 1346 isDefinition: false, 1347 lineText: ` return evaluateKeyboardEvent();` 1348 }), 1349 ], 1350 symbolName: searchStr, 1351 symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset, 1352 symbolDisplayString: "function evaluateKeyboardEvent(): void" 1353 }); 1354 } 1355 1356 it(`when using declaration file maps to navigate between projects`, () => { 1357 verify(/*disableSourceOfProjectReferenceRedirect*/ true); 1358 }); 1359 it(`when using original source files in the project`, () => { 1360 verify(/*disableSourceOfProjectReferenceRedirect*/ false); 1361 }); 1362 }); 1363 1364 it("reusing d.ts files from composite and non composite projects", () => { 1365 const configA: File = { 1366 path: `${tscWatch.projectRoot}/compositea/tsconfig.json`, 1367 content: JSON.stringify({ 1368 compilerOptions: { 1369 composite: true, 1370 outDir: "../dist/", 1371 rootDir: "../", 1372 baseUrl: "../", 1373 paths: { "@ref/*": ["./dist/*"] } 1374 } 1375 }) 1376 }; 1377 const aTs: File = { 1378 path: `${tscWatch.projectRoot}/compositea/a.ts`, 1379 content: `import { b } from "@ref/compositeb/b";` 1380 }; 1381 const a2Ts: File = { 1382 path: `${tscWatch.projectRoot}/compositea/a2.ts`, 1383 content: `export const x = 10;` 1384 }; 1385 const configB: File = { 1386 path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`, 1387 content: configA.content 1388 }; 1389 const bTs: File = { 1390 path: `${tscWatch.projectRoot}/compositeb/b.ts`, 1391 content: "export function b() {}" 1392 }; 1393 const bDts: File = { 1394 path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`, 1395 content: "export declare function b(): void;" 1396 }; 1397 const configC: File = { 1398 path: `${tscWatch.projectRoot}/compositec/tsconfig.json`, 1399 content: JSON.stringify({ 1400 compilerOptions: { 1401 composite: true, 1402 outDir: "../dist/", 1403 rootDir: "../", 1404 baseUrl: "../", 1405 paths: { "@ref/*": ["./*"] } 1406 }, 1407 references: [{ path: "../compositeb" }] 1408 }) 1409 }; 1410 const cTs: File = { 1411 path: `${tscWatch.projectRoot}/compositec/c.ts`, 1412 content: aTs.content 1413 }; 1414 const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC]; 1415 const host = createServerHost(files); 1416 const service = createProjectService(host); 1417 service.openClientFile(aTs.path); 1418 service.checkNumberOfProjects({ configuredProjects: 1 }); 1419 1420 // project A referencing b.d.ts without project reference 1421 const projectA = service.configuredProjects.get(configA.path)!; 1422 assert.isDefined(projectA); 1423 checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]); 1424 1425 // reuses b.d.ts but sets the path and resolved path since projectC has project references 1426 // as the real resolution was to b.ts 1427 service.openClientFile(cTs.path); 1428 service.checkNumberOfProjects({ configuredProjects: 2 }); 1429 const projectC = service.configuredProjects.get(configC.path)!; 1430 checkProjectActualFiles(projectC, [cTs.path, bTs.path, libFile.path, configC.path]); 1431 1432 // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location 1433 host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`); 1434 assert.isTrue(projectA.dirty); 1435 projectA.updateGraph(); 1436 }); 1437 1438 describe("when references are monorepo like with symlinks", () => { 1439 interface Packages { 1440 bPackageJson: File; 1441 aTest: File; 1442 bFoo: File; 1443 bBar: File; 1444 bSymlink: SymLink; 1445 } 1446 function verifySymlinkScenario(packages: () => Packages) { 1447 describe("when solution is not built", () => { 1448 it("with preserveSymlinks turned off", () => { 1449 verifySession(packages(), /*alreadyBuilt*/ false, {}); 1450 }); 1451 1452 it("with preserveSymlinks turned on", () => { 1453 verifySession(packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true }); 1454 }); 1455 }); 1456 1457 describe("when solution is already built", () => { 1458 it("with preserveSymlinks turned off", () => { 1459 verifySession(packages(), /*alreadyBuilt*/ true, {}); 1460 }); 1461 1462 it("with preserveSymlinks turned on", () => { 1463 verifySession(packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true }); 1464 }); 1465 }); 1466 } 1467 1468 function verifySession({ bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) { 1469 const aConfig = config("A", extraOptions, ["../B"]); 1470 const bConfig = config("B", extraOptions); 1471 const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; 1472 const host = alreadyBuilt ? 1473 createHost(files, [aConfig.path]) : 1474 createServerHost(files); 1475 1476 // Create symlink in node module 1477 const session = createSession(host, { canUseEvents: true }); 1478 openFilesForSession([aTest], session); 1479 const service = session.getProjectService(); 1480 const project = service.configuredProjects.get(aConfig.path.toLowerCase())!; 1481 assert.deepEqual(project.getAllProjectErrors(), []); 1482 checkProjectActualFiles( 1483 project, 1484 [aConfig.path, aTest.path, bFoo.path, bBar.path, libFile.path] 1485 ); 1486 verifyGetErrRequestNoErrors({ session, host, files: [aTest] }); 1487 session.executeCommandSeq<protocol.UpdateOpenRequest>({ 1488 command: protocol.CommandTypes.UpdateOpen, 1489 arguments: { 1490 changedFiles: [{ 1491 fileName: aTest.path, 1492 textChanges: [{ 1493 newText: "\n", 1494 start: { line: 5, offset: 1 }, 1495 end: { line: 5, offset: 1 } 1496 }] 1497 }] 1498 } 1499 }); 1500 verifyGetErrRequestNoErrors({ session, host, files: [aTest] }); 1501 } 1502 1503 function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File { 1504 return { 1505 path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`, 1506 content: JSON.stringify({ 1507 compilerOptions: { 1508 outDir: "lib", 1509 rootDir: "src", 1510 composite: true, 1511 ...extraOptions 1512 }, 1513 include: ["src"], 1514 ...(references ? { references: references.map(path => ({ path })) } : {}) 1515 }) 1516 }; 1517 } 1518 1519 function file(packageName: string, fileName: string, content: string): File { 1520 return { 1521 path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`, 1522 content 1523 }; 1524 } 1525 1526 function verifyMonoRepoLike(scope = "") { 1527 describe("when packageJson has types field and has index.ts", () => { 1528 verifySymlinkScenario(() => ({ 1529 bPackageJson: { 1530 path: `${tscWatch.projectRoot}/packages/B/package.json`, 1531 content: JSON.stringify({ 1532 main: "lib/index.js", 1533 types: "lib/index.d.ts" 1534 }) 1535 }, 1536 aTest: file("A", "index.ts", `import { foo } from '${scope}b'; 1537import { bar } from '${scope}b/lib/bar'; 1538foo(); 1539bar(); 1540`), 1541 bFoo: file("B", "index.ts", `export function foo() { }`), 1542 bBar: file("B", "bar.ts", `export function bar() { }`), 1543 bSymlink: { 1544 path: `${tscWatch.projectRoot}/node_modules/${scope}b`, 1545 symLink: `${tscWatch.projectRoot}/packages/B` 1546 } 1547 })); 1548 }); 1549 1550 describe("when referencing file from subFolder", () => { 1551 verifySymlinkScenario(() => ({ 1552 bPackageJson: { 1553 path: `${tscWatch.projectRoot}/packages/B/package.json`, 1554 content: "{}" 1555 }, 1556 aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo'; 1557import { bar } from '${scope}b/lib/bar/foo'; 1558foo(); 1559bar(); 1560`), 1561 bFoo: file("B", "foo.ts", `export function foo() { }`), 1562 bBar: file("B", "bar/foo.ts", `export function bar() { }`), 1563 bSymlink: { 1564 path: `${tscWatch.projectRoot}/node_modules/${scope}b`, 1565 symLink: `${tscWatch.projectRoot}/packages/B` 1566 } 1567 })); 1568 }); 1569 } 1570 describe("when package is not scoped", () => { 1571 verifyMonoRepoLike(); 1572 }); 1573 describe("when package is scoped", () => { 1574 verifyMonoRepoLike("@issue/"); 1575 }); 1576 }); 1577 1578 it("when finding local reference doesnt load ancestor/sibling projects", () => { 1579 const solutionLocation = "/user/username/projects/solution"; 1580 const solution: File = { 1581 path: `${solutionLocation}/tsconfig.json`, 1582 content: JSON.stringify({ 1583 files: [], 1584 include: [], 1585 references: [ 1586 { path: "./compiler" }, 1587 { path: "./services" }, 1588 ] 1589 }) 1590 }; 1591 const compilerConfig: File = { 1592 path: `${solutionLocation}/compiler/tsconfig.json`, 1593 content: JSON.stringify({ 1594 compilerOptions: { 1595 composite: true, 1596 module: "none" 1597 }, 1598 files: ["./types.ts", "./program.ts"] 1599 }) 1600 }; 1601 const typesFile: File = { 1602 path: `${solutionLocation}/compiler/types.ts`, 1603 content: ` 1604 namespace ts { 1605 export interface Program { 1606 getSourceFiles(): string[]; 1607 } 1608 }` 1609 }; 1610 const programFile: File = { 1611 path: `${solutionLocation}/compiler/program.ts`, 1612 content: ` 1613 namespace ts { 1614 export const program: Program = { 1615 getSourceFiles: () => [getSourceFile()] 1616 }; 1617 function getSourceFile() { return "something"; } 1618 }` 1619 }; 1620 const servicesConfig: File = { 1621 path: `${solutionLocation}/services/tsconfig.json`, 1622 content: JSON.stringify({ 1623 compilerOptions: { 1624 composite: true 1625 }, 1626 files: ["./services.ts"], 1627 references: [ 1628 { path: "../compiler" } 1629 ] 1630 }) 1631 }; 1632 const servicesFile: File = { 1633 path: `${solutionLocation}/services/services.ts`, 1634 content: ` 1635 namespace ts { 1636 const result = program.getSourceFiles(); 1637 }` 1638 }; 1639 1640 const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; 1641 const host = createServerHost(files); 1642 const session = createSession(host); 1643 const service = session.getProjectService(); 1644 service.openClientFile(programFile.path); 1645 checkNumberOfProjects(service, { configuredProjects: 2 }); 1646 const compilerProject = service.configuredProjects.get(compilerConfig.path)!; 1647 checkProjectActualFiles(compilerProject, [libFile.path, typesFile.path, programFile.path, compilerConfig.path]); 1648 const solutionProject = service.configuredProjects.get(solution.path)!; 1649 assert.isTrue(solutionProject.isInitialLoadPending()); 1650 1651 // Find all references for getSourceFile 1652 const response = session.executeCommandSeq<protocol.ReferencesRequest>({ 1653 command: protocol.CommandTypes.References, 1654 arguments: protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 }) 1655 }).response as protocol.ReferencesResponseBody; 1656 assert.deepEqual(response, { 1657 refs: [ 1658 makeReferenceItem({ 1659 file: programFile, 1660 text: "getSourceFile", 1661 options: { index: 1 }, 1662 isDefinition: false, 1663 lineText: ` getSourceFiles: () => [getSourceFile()]`, 1664 }), 1665 makeReferenceItem({ 1666 file: programFile, 1667 text: "getSourceFile", 1668 options: { index: 2 }, 1669 contextText: `function getSourceFile() { return "something"; }`, 1670 isDefinition: true, 1671 lineText: ` function getSourceFile() { return "something"; }`, 1672 }) 1673 ], 1674 symbolName: "getSourceFile", 1675 symbolStartOffset: protocolLocationFromSubstring(programFile.content, "getSourceFile", { index: 1 }).offset, 1676 symbolDisplayString: "function getSourceFile(): string" 1677 }); 1678 // Shouldnt load more projects 1679 checkNumberOfProjects(service, { configuredProjects: 2 }); 1680 assert.isTrue(solutionProject.isInitialLoadPending()); 1681 1682 // Find all references for getSourceFiles 1683 const getSourceFilesResponse = session.executeCommandSeq<protocol.ReferencesRequest>({ 1684 command: protocol.CommandTypes.References, 1685 arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") 1686 }).response as protocol.ReferencesResponseBody; 1687 assert.deepEqual(getSourceFilesResponse, { 1688 refs: [ 1689 makeReferenceItem({ 1690 file: typesFile, 1691 text: "getSourceFiles", 1692 contextText: `getSourceFiles(): string[];`, 1693 isDefinition: true, 1694 isWriteAccess: false, 1695 lineText: ` getSourceFiles(): string[];`, 1696 }), 1697 makeReferenceItem({ 1698 file: programFile, 1699 text: "getSourceFiles", 1700 contextText: `getSourceFiles: () => [getSourceFile()]`, 1701 isDefinition: true, 1702 lineText: ` getSourceFiles: () => [getSourceFile()]`, 1703 }), 1704 makeReferenceItem({ 1705 file: servicesFile, 1706 text: "getSourceFiles", 1707 isDefinition: false, 1708 lineText: ` const result = program.getSourceFiles();`, 1709 }) 1710 ], 1711 symbolName: "getSourceFiles", 1712 symbolStartOffset: protocolLocationFromSubstring(typesFile.content, "getSourceFiles").offset, 1713 symbolDisplayString: "(method) ts.Program.getSourceFiles(): string[]" 1714 }); 1715 1716 // Should load more projects 1717 checkNumberOfProjects(service, { configuredProjects: 3 }); 1718 assert.isFalse(solutionProject.isInitialLoadPending()); 1719 checkProjectActualFiles(solutionProject, [solution.path]); 1720 checkProjectActualFiles(service.configuredProjects.get(servicesConfig.path)!, [servicesFile.path, servicesConfig.path, libFile.path, typesFile.path, programFile.path]); 1721 }); 1722 1723 it("when disableSolutionSearching is true, solution and siblings are not loaded", () => { 1724 const solutionLocation = "/user/username/projects/solution"; 1725 const solution: File = { 1726 path: `${solutionLocation}/tsconfig.json`, 1727 content: JSON.stringify({ 1728 files: [], 1729 include: [], 1730 references: [ 1731 { path: "./compiler" }, 1732 { path: "./services" }, 1733 ] 1734 }) 1735 }; 1736 const compilerConfig: File = { 1737 path: `${solutionLocation}/compiler/tsconfig.json`, 1738 content: JSON.stringify({ 1739 compilerOptions: { 1740 composite: true, 1741 module: "none", 1742 disableSolutionSearching: true 1743 }, 1744 files: ["./types.ts", "./program.ts"] 1745 }) 1746 }; 1747 const typesFile: File = { 1748 path: `${solutionLocation}/compiler/types.ts`, 1749 content: ` 1750 namespace ts { 1751 export interface Program { 1752 getSourceFiles(): string[]; 1753 } 1754 }` 1755 }; 1756 const programFile: File = { 1757 path: `${solutionLocation}/compiler/program.ts`, 1758 content: ` 1759 namespace ts { 1760 export const program: Program = { 1761 getSourceFiles: () => [getSourceFile()] 1762 }; 1763 function getSourceFile() { return "something"; } 1764 }` 1765 }; 1766 const servicesConfig: File = { 1767 path: `${solutionLocation}/services/tsconfig.json`, 1768 content: JSON.stringify({ 1769 compilerOptions: { 1770 composite: true 1771 }, 1772 files: ["./services.ts"], 1773 references: [ 1774 { path: "../compiler" } 1775 ] 1776 }) 1777 }; 1778 const servicesFile: File = { 1779 path: `${solutionLocation}/services/services.ts`, 1780 content: ` 1781 namespace ts { 1782 const result = program.getSourceFiles(); 1783 }` 1784 }; 1785 1786 const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile]; 1787 const host = createServerHost(files); 1788 const session = createSession(host); 1789 const service = session.getProjectService(); 1790 service.openClientFile(programFile.path); 1791 checkNumberOfProjects(service, { configuredProjects: 1 }); 1792 const compilerProject = service.configuredProjects.get(compilerConfig.path)!; 1793 checkProjectActualFiles(compilerProject, [libFile.path, typesFile.path, programFile.path, compilerConfig.path]); 1794 1795 // Find all references 1796 const getSourceFilesResponse = session.executeCommandSeq<protocol.ReferencesRequest>({ 1797 command: protocol.CommandTypes.References, 1798 arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles") 1799 }).response as protocol.ReferencesResponseBody; 1800 assert.deepEqual(getSourceFilesResponse, { 1801 refs: [ 1802 makeReferenceItem({ 1803 file: typesFile, 1804 text: "getSourceFiles", 1805 contextText: `getSourceFiles(): string[];`, 1806 isDefinition: true, 1807 isWriteAccess: false, 1808 lineText: ` getSourceFiles(): string[];`, 1809 }), 1810 makeReferenceItem({ 1811 file: programFile, 1812 text: "getSourceFiles", 1813 contextText: `getSourceFiles: () => [getSourceFile()]`, 1814 isDefinition: true, 1815 lineText: ` getSourceFiles: () => [getSourceFile()]`, 1816 }), 1817 ], 1818 symbolName: "getSourceFiles", 1819 symbolStartOffset: protocolLocationFromSubstring(typesFile.content, "getSourceFiles").offset, 1820 symbolDisplayString: "(method) ts.Program.getSourceFiles(): string[]" 1821 }); 1822 1823 // No new solutions/projects loaded 1824 checkNumberOfProjects(service, { configuredProjects: 1 }); 1825 }); 1826 1827 describe("when default project is solution project", () => { 1828 interface Setup { 1829 solutionOptions?: CompilerOptions; 1830 solutionFiles?: string[]; 1831 configRefs: string[]; 1832 additionalFiles: readonly File[]; 1833 expectedOpenEvents: protocol.Event[]; 1834 } 1835 interface VerifySolutionScenario extends Setup { 1836 solutionProject?: readonly string[]; 1837 additionalProjects: readonly { projectName: string, files: readonly string[] }[]; 1838 expectedReloadEvents: protocol.Event[]; 1839 expectedReferences: protocol.ReferencesResponseBody; 1840 expectedReferencesFromDtsProject: protocol.ReferencesResponseBody; 1841 } 1842 const main: File = { 1843 path: `${tscWatch.projectRoot}/src/main.ts`, 1844 content: `import { foo } from 'helpers/functions'; 1845export { foo };` 1846 }; 1847 const helper: File = { 1848 path: `${tscWatch.projectRoot}/src/helpers/functions.ts`, 1849 content: `export const foo = 1;` 1850 }; 1851 const mainDts: File = { 1852 path: `${tscWatch.projectRoot}/target/src/main.d.ts`, 1853 content: `import { foo } from 'helpers/functions'; 1854export { foo }; 1855//# sourceMappingURL=main.d.ts.map` 1856 }; 1857 const mainDtsMap: File = { 1858 path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`, 1859 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"}` 1860 }; 1861 const helperDts: File = { 1862 path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`, 1863 content: `export declare const foo = 1; 1864//# sourceMappingURL=functions.d.ts.map` 1865 }; 1866 const helperDtsMap: File = { 1867 path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`, 1868 content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}` 1869 }; 1870 const tsconfigIndirect3: File = { 1871 path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`, 1872 content: JSON.stringify({ 1873 compilerOptions: { 1874 baseUrl: "../target/src/" 1875 }, 1876 }) 1877 }; 1878 const fileResolvingToMainDts: File = { 1879 path: `${tscWatch.projectRoot}/indirect3/main.ts`, 1880 content: `import { foo } from 'main'; 1881foo; 1882export function bar() {}` 1883 }; 1884 const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`; 1885 const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`; 1886 const dummyFilePath = "/dummy/dummy.ts"; 1887 function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles, expectedOpenEvents }: Setup) { 1888 const tsconfigSrc: File = { 1889 path: tsconfigSrcPath, 1890 content: JSON.stringify({ 1891 compilerOptions: { 1892 composite: true, 1893 outDir: "./target/", 1894 baseUrl: "./src/" 1895 }, 1896 include: ["./src/**/*"] 1897 }) 1898 }; 1899 const tsconfig: File = { 1900 path: tsconfigPath, 1901 content: JSON.stringify({ 1902 ... (solutionOptions ? { compilerOptions: solutionOptions } : {}), 1903 references: configRefs.map(path => ({ path })), 1904 files: solutionFiles || [] 1905 }) 1906 }; 1907 const dummyFile: File = { 1908 path: dummyFilePath, 1909 content: "let a = 10;" 1910 }; 1911 const host = createServerHost([ 1912 tsconfigSrc, tsconfig, main, helper, 1913 libFile, dummyFile, 1914 mainDts, mainDtsMap, helperDts, helperDtsMap, 1915 tsconfigIndirect3, fileResolvingToMainDts, 1916 ...additionalFiles]); 1917 const session = createSession(host, { canUseEvents: true }); 1918 const service = session.getProjectService(); 1919 service.openClientFile(main.path); 1920 checkEvents(session, expectedOpenEvents); 1921 return { session, service, host, tsconfigSrc, tsconfig }; 1922 } 1923 1924 function verifySolutionScenario(input: VerifySolutionScenario) { 1925 const { session, service, host, tsconfigSrc, tsconfig } = setup(input); 1926 const { 1927 solutionProject, additionalProjects, expectedReloadEvents, 1928 expectedReferences, expectedReferencesFromDtsProject 1929 } = input; 1930 verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false); 1931 const info = service.getScriptInfoForPath(main.path as Path)!; 1932 const project = service.configuredProjects.get(tsconfigSrc.path)!; 1933 assert.equal(info.getDefaultProject(), project); 1934 assert.equal(service.findDefaultConfiguredProject(info), project); 1935 1936 // Verify errors 1937 verifyGetErrRequestNoErrors({ session, host, files: [main] }); 1938 1939 // Verify collection of script infos 1940 service.openClientFile(dummyFilePath); 1941 verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); 1942 1943 service.closeClientFile(main.path); 1944 service.closeClientFile(dummyFilePath); 1945 service.openClientFile(dummyFilePath); 1946 verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true); 1947 1948 service.openClientFile(main.path); 1949 service.closeClientFile(dummyFilePath); 1950 service.openClientFile(dummyFilePath); 1951 verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); 1952 1953 // Verify Reload projects 1954 session.clearMessages(); 1955 service.reloadProjects(); 1956 checkEvents(session, expectedReloadEvents); 1957 verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true); 1958 1959 // Find all refs 1960 const response = session.executeCommandSeq<protocol.ReferencesRequest>({ 1961 command: protocol.CommandTypes.References, 1962 arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 }) 1963 }).response as protocol.ReferencesResponseBody; 1964 assert.deepEqual(response, expectedReferences); 1965 1966 service.closeClientFile(main.path); 1967 service.closeClientFile(dummyFilePath); 1968 1969 // Verify when declaration map references the file 1970 service.openClientFile(fileResolvingToMainDts.path); 1971 checkNumberOfProjects(service, { configuredProjects: 1 }); 1972 checkProjectActualFiles(service.configuredProjects.get(tsconfigIndirect3.path)!, [tsconfigIndirect3.path, fileResolvingToMainDts.path, mainDts.path, helperDts.path, libFile.path]); 1973 1974 // Find all refs from dts include 1975 const response2 = session.executeCommandSeq<protocol.ReferencesRequest>({ 1976 command: protocol.CommandTypes.References, 1977 arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo") 1978 }).response as protocol.ReferencesResponseBody; 1979 assert.deepEqual(response2, expectedReferencesFromDtsProject); 1980 1981 function verifyProjects(includeConfigured: boolean, includeDummy: boolean) { 1982 const inferredProjects = includeDummy ? 1 : 0; 1983 const configuredProjects = includeConfigured ? additionalProjects.length + 2 : 0; 1984 checkNumberOfProjects(service, { configuredProjects, inferredProjects }); 1985 if (includeConfigured) { 1986 checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]); 1987 checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, solutionProject || [tsconfig.path]); 1988 additionalProjects.forEach(({ projectName, files }) => 1989 checkProjectActualFiles(service.configuredProjects.get(projectName)!, files)); 1990 } 1991 if (includeDummy) { 1992 checkProjectActualFiles(service.inferredProjects[0], [dummyFilePath, libFile.path]); 1993 } 1994 } 1995 } 1996 1997 function expectedProjectLoadAndTelemetry(config: string, reason: string) { 1998 return [ 1999 projectLoadingStartEvent(config, reason), 2000 projectLoadingFinishEvent(config), 2001 projectInfoTelemetryEvent(), 2002 ]; 2003 } 2004 2005 function expectedSolutionLoadAndTelemetry() { 2006 return expectedProjectLoadAndTelemetry(tsconfigPath, `Creating possible configured project for ${main.path} to open`); 2007 } 2008 2009 function expectedProjectReferenceLoadAndTelemetry(config: string) { 2010 return expectedProjectLoadAndTelemetry(config, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${main.path} to open`); 2011 } 2012 2013 function expectedReloadEvent(config: string) { 2014 return [ 2015 projectLoadingStartEvent(config, `User requested reload projects`), 2016 projectLoadingFinishEvent(config), 2017 configFileDiagEvent(config, config, []) 2018 ]; 2019 } 2020 2021 function expectedReferencesResponse(): protocol.ReferencesResponseBody { 2022 return { 2023 refs: [ 2024 makeReferenceItem({ 2025 file: main, 2026 text: "foo", 2027 contextText: `import { foo } from 'helpers/functions';`, 2028 isDefinition: true, 2029 isWriteAccess: true, 2030 lineText: `import { foo } from 'helpers/functions';`, 2031 }), 2032 makeReferenceItem({ 2033 file: main, 2034 text: "foo", 2035 options: { index: 1 }, 2036 contextText: `export { foo };`, 2037 isDefinition: true, 2038 isWriteAccess: true, 2039 lineText: `export { foo };`, 2040 }), 2041 makeReferenceItem({ 2042 file: helper, 2043 text: "foo", 2044 contextText: `export const foo = 1;`, 2045 isDefinition: true, 2046 isWriteAccess: true, 2047 lineText: `export const foo = 1;`, 2048 }), 2049 ], 2050 symbolName: "foo", 2051 symbolStartOffset: protocolLocationFromSubstring(main.content, "foo").offset, 2052 symbolDisplayString: "(alias) const foo: 1\nexport foo" 2053 }; 2054 } 2055 2056 function expectedIndirectRefs(indirect: File) { 2057 return [ 2058 makeReferenceItem({ 2059 file: indirect, 2060 text: "foo", 2061 contextText: `import { foo } from 'main';`, 2062 isDefinition: true, 2063 isWriteAccess: true, 2064 lineText: `import { foo } from 'main';`, 2065 }), 2066 makeReferenceItem({ 2067 file: indirect, 2068 text: "foo", 2069 options: { index: 1 }, 2070 isDefinition: false, 2071 isWriteAccess: false, 2072 lineText: `foo;`, 2073 }), 2074 ]; 2075 } 2076 2077 function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) { 2078 const tsconfigIndirect: File = { 2079 path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`, 2080 content: JSON.stringify({ 2081 compilerOptions: { 2082 composite: true, 2083 outDir: "./target/", 2084 baseUrl: "./src/", 2085 ...optionsToExtend 2086 }, 2087 files: [`./indirect${postfix}/main.ts`], 2088 references: [{ path: "./tsconfig-src.json" }] 2089 }) 2090 }; 2091 const indirect: File = { 2092 path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`, 2093 content: fileResolvingToMainDts.content 2094 }; 2095 return { tsconfigIndirect, indirect }; 2096 } 2097 2098 interface VerifyProjects { 2099 configuredProjects: readonly { projectName: string, files: readonly string[] }[]; 2100 inferredProjects: readonly (readonly string[])[]; 2101 } 2102 2103 interface VerifyDisableReferencedProjectLoad extends Setup { 2104 expectedProjectsOnOpen: VerifyProjects; 2105 expectedProjectsOnDummyOpen?: VerifyProjects; 2106 expectedProjectsOnReload?: VerifyProjects; 2107 expectedDefaultProject: (service: server.ProjectService) => server.Project; 2108 expectedDefaultConfiguredProject: (service: server.ProjectService) => server.ConfiguredProject | undefined; 2109 expectedReloadEvents: protocol.Event[]; 2110 } 2111 2112 function verifyDisableReferencedProjectLoad(input: VerifyDisableReferencedProjectLoad) { 2113 const { session, service } = setup(input); 2114 const { expectedProjectsOnOpen, expectedDefaultProject, expectedDefaultConfiguredProject, expectedReloadEvents } = input; 2115 const expectedProjectsOnOnlyDummy: VerifyProjects = { 2116 configuredProjects: emptyArray, 2117 inferredProjects: [ 2118 [dummyFilePath, libFile.path], 2119 ] 2120 }; 2121 const expectedProjectsOnDummyOpen = input.expectedProjectsOnDummyOpen || { 2122 configuredProjects: expectedProjectsOnOpen.configuredProjects, 2123 inferredProjects: expectedProjectsOnOnlyDummy.inferredProjects, 2124 }; 2125 const expectedProjectsOnReload = input.expectedProjectsOnReload || expectedProjectsOnDummyOpen; 2126 2127 verifyProjects(expectedProjectsOnOpen); 2128 const info = service.getScriptInfoForPath(main.path as Path)!; 2129 assert.equal(info.getDefaultProject(), expectedDefaultProject(service)); 2130 assert.equal(service.findDefaultConfiguredProject(info), expectedDefaultConfiguredProject(service)); 2131 2132 // Verify collection of script infos 2133 service.openClientFile(dummyFilePath); 2134 verifyProjects(expectedProjectsOnDummyOpen); 2135 2136 service.closeClientFile(main.path); 2137 service.closeClientFile(dummyFilePath); 2138 service.openClientFile(dummyFilePath); 2139 verifyProjects(expectedProjectsOnOnlyDummy); 2140 2141 service.openClientFile(main.path); 2142 2143 // Verify Reload projects 2144 session.clearMessages(); 2145 service.reloadProjects(); 2146 checkEvents(session, expectedReloadEvents); 2147 verifyProjects(expectedProjectsOnReload); 2148 2149 function verifyProjects(expected: VerifyProjects) { 2150 checkNumberOfProjects(service, { configuredProjects: expected.configuredProjects.length, inferredProjects: expected.inferredProjects.length }); 2151 expected.configuredProjects.forEach(({ projectName, files }) => 2152 checkProjectActualFiles(service.configuredProjects.get(projectName)!, files)); 2153 expected.inferredProjects.forEach((files, index) => 2154 checkProjectActualFiles(service.inferredProjects[index], files)); 2155 } 2156 } 2157 2158 it("when project is directly referenced by solution", () => { 2159 const expectedReferences = expectedReferencesResponse(); 2160 verifySolutionScenario({ 2161 configRefs: ["./tsconfig-src.json"], 2162 additionalFiles: emptyArray, 2163 additionalProjects: emptyArray, 2164 expectedOpenEvents: [ 2165 ...expectedSolutionLoadAndTelemetry(), 2166 ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), 2167 configFileDiagEvent(main.path, tsconfigSrcPath, []) 2168 ], 2169 expectedReloadEvents: [ 2170 ...expectedReloadEvent(tsconfigPath), 2171 ...expectedReloadEvent(tsconfigSrcPath), 2172 ], 2173 expectedReferences, 2174 expectedReferencesFromDtsProject: { 2175 ...expectedReferences, 2176 refs: [ 2177 ...expectedIndirectRefs(fileResolvingToMainDts), 2178 ...expectedReferences.refs 2179 ], 2180 symbolDisplayString: "(alias) const foo: 1\nimport foo", 2181 } 2182 }); 2183 }); 2184 2185 it("when project is indirectly referenced by solution", () => { 2186 const { tsconfigIndirect, indirect } = getIndirectProject("1"); 2187 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 2188 const { refs, ...rest } = expectedReferencesResponse(); 2189 verifySolutionScenario({ 2190 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 2191 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], 2192 additionalProjects: emptyArray, 2193 expectedOpenEvents: [ 2194 ...expectedSolutionLoadAndTelemetry(), 2195 ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), 2196 configFileDiagEvent(main.path, tsconfigSrcPath, []) 2197 ], 2198 expectedReloadEvents: [ 2199 ...expectedReloadEvent(tsconfigPath), 2200 ...expectedReloadEvent(tsconfigSrcPath), 2201 ], 2202 expectedReferences: { 2203 refs: [ 2204 ...refs, 2205 ...expectedIndirectRefs(indirect), 2206 ...expectedIndirectRefs(indirect2), 2207 ], 2208 ...rest 2209 }, 2210 expectedReferencesFromDtsProject: { 2211 ...rest, 2212 refs: [ 2213 ...expectedIndirectRefs(fileResolvingToMainDts), 2214 ...refs, 2215 ...expectedIndirectRefs(indirect), 2216 ...expectedIndirectRefs(indirect2), 2217 ], 2218 symbolDisplayString: "(alias) const foo: 1\nimport foo", 2219 } 2220 }); 2221 }); 2222 2223 it("disables looking into the child project if disableReferencedProjectLoad is set", () => { 2224 const expectedProjectsOnOpen: VerifyProjects = { 2225 configuredProjects: [ 2226 { projectName: tsconfigPath, files: [tsconfigPath] }, 2227 ], 2228 inferredProjects: [ 2229 [main.path, libFile.path], 2230 ] 2231 }; 2232 verifyDisableReferencedProjectLoad({ 2233 solutionOptions: { disableReferencedProjectLoad: true }, 2234 configRefs: ["./tsconfig-src.json"], 2235 additionalFiles: emptyArray, 2236 expectedOpenEvents: [ 2237 ...expectedSolutionLoadAndTelemetry(), 2238 configFileDiagEvent(main.path, tsconfigPath, []) 2239 ], 2240 expectedDefaultProject: service => service.inferredProjects[0], 2241 expectedDefaultConfiguredProject: returnUndefined, 2242 expectedProjectsOnOpen, 2243 expectedProjectsOnDummyOpen: { 2244 configuredProjects: emptyArray, 2245 inferredProjects: [ 2246 ...expectedProjectsOnOpen.inferredProjects, 2247 [dummyFilePath, libFile.path], 2248 ] 2249 }, 2250 expectedProjectsOnReload: { 2251 configuredProjects: expectedProjectsOnOpen.configuredProjects, 2252 inferredProjects: [ 2253 [dummyFilePath, libFile.path], 2254 ...expectedProjectsOnOpen.inferredProjects, 2255 ] 2256 }, 2257 expectedReloadEvents: expectedReloadEvent(tsconfigPath) 2258 }); 2259 }); 2260 2261 it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { 2262 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 2263 const expectedProjectsOnOpen: VerifyProjects = { 2264 configuredProjects: [ 2265 { projectName: tsconfigPath, files: [tsconfigPath] }, 2266 { projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] }, 2267 ], 2268 inferredProjects: emptyArray 2269 }; 2270 verifyDisableReferencedProjectLoad({ 2271 configRefs: ["./tsconfig-indirect1.json"], 2272 additionalFiles: [tsconfigIndirect, indirect], 2273 expectedOpenEvents: [ 2274 ...expectedSolutionLoadAndTelemetry(), 2275 ...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path), 2276 configFileDiagEvent(main.path, tsconfigIndirect.path, []) 2277 ], 2278 expectedDefaultProject: service => service.configuredProjects.get(tsconfigIndirect.path)!, 2279 expectedDefaultConfiguredProject: returnUndefined, 2280 expectedProjectsOnOpen, 2281 expectedReloadEvents: [ 2282 ...expectedReloadEvent(tsconfigPath), 2283 ...expectedReloadEvent(tsconfigIndirect.path), 2284 ] 2285 }); 2286 }); 2287 2288 it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { 2289 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 2290 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 2291 const expectedProjectsOnOpen: VerifyProjects = { 2292 configuredProjects: [ 2293 { projectName: tsconfigPath, files: [tsconfigPath] }, 2294 { projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] }, 2295 ], 2296 inferredProjects: emptyArray 2297 }; 2298 verifyDisableReferencedProjectLoad({ 2299 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 2300 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2], 2301 expectedOpenEvents: [ 2302 ...expectedSolutionLoadAndTelemetry(), 2303 ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), 2304 configFileDiagEvent(main.path, tsconfigSrcPath, []) 2305 ], 2306 expectedDefaultProject: service => service.configuredProjects.get(tsconfigSrcPath)!, 2307 expectedDefaultConfiguredProject: service => service.configuredProjects.get(tsconfigSrcPath)!, 2308 expectedProjectsOnOpen, 2309 expectedReloadEvents: [ 2310 ...expectedReloadEvent(tsconfigPath), 2311 ...expectedReloadEvent(tsconfigSrcPath), 2312 ] 2313 }); 2314 }); 2315 2316 describe("when solution is project that contains its own files", () => { 2317 it("when the project found is not solution but references open file through project reference", () => { 2318 const ownMain: File = { 2319 path: `${tscWatch.projectRoot}/own/main.ts`, 2320 content: fileResolvingToMainDts.content 2321 }; 2322 const { refs, ...rest } = expectedReferencesResponse(); 2323 verifySolutionScenario({ 2324 solutionFiles: [`./own/main.ts`], 2325 solutionOptions: { 2326 outDir: "./target/", 2327 baseUrl: "./src/" 2328 }, 2329 solutionProject: [tsconfigPath, ownMain.path, main.path, libFile.path, helper.path], 2330 configRefs: ["./tsconfig-src.json"], 2331 additionalFiles: [ownMain], 2332 additionalProjects: emptyArray, 2333 expectedOpenEvents: [ 2334 ...expectedSolutionLoadAndTelemetry(), 2335 ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), 2336 configFileDiagEvent(main.path, tsconfigSrcPath, []) 2337 ], 2338 expectedReloadEvents: [ 2339 ...expectedReloadEvent(tsconfigPath), 2340 ...expectedReloadEvent(tsconfigSrcPath), 2341 ], 2342 expectedReferences: { 2343 refs: [ 2344 ...refs, 2345 ...expectedIndirectRefs(ownMain), 2346 ], 2347 ...rest 2348 }, 2349 expectedReferencesFromDtsProject: { 2350 ...rest, 2351 refs: [ 2352 ...expectedIndirectRefs(fileResolvingToMainDts), 2353 ...refs, 2354 ...expectedIndirectRefs(ownMain), 2355 ], 2356 symbolDisplayString: "(alias) const foo: 1\nimport foo", 2357 }, 2358 }); 2359 }); 2360 2361 it("when project is indirectly referenced by solution", () => { 2362 const ownMain: File = { 2363 path: `${tscWatch.projectRoot}/own/main.ts`, 2364 content: `import { bar } from 'main'; 2365bar;` 2366 }; 2367 const { tsconfigIndirect, indirect } = getIndirectProject("1"); 2368 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 2369 const { refs, ...rest } = expectedReferencesResponse(); 2370 verifySolutionScenario({ 2371 solutionFiles: [`./own/main.ts`], 2372 solutionOptions: { 2373 outDir: "./target/", 2374 baseUrl: "./indirect1/" 2375 }, 2376 solutionProject: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path], 2377 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 2378 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], 2379 additionalProjects: emptyArray, 2380 expectedOpenEvents: [ 2381 ...expectedSolutionLoadAndTelemetry(), 2382 ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), 2383 configFileDiagEvent(main.path, tsconfigSrcPath, []) 2384 ], 2385 expectedReloadEvents: [ 2386 ...expectedReloadEvent(tsconfigPath), 2387 ...expectedReloadEvent(tsconfigSrcPath), 2388 ], 2389 expectedReferences: { 2390 refs: [ 2391 ...refs, 2392 ...expectedIndirectRefs(indirect), 2393 ...expectedIndirectRefs(indirect2), 2394 ], 2395 ...rest 2396 }, 2397 expectedReferencesFromDtsProject: { 2398 ...rest, 2399 refs: [ 2400 ...expectedIndirectRefs(fileResolvingToMainDts), 2401 ...refs, 2402 ...expectedIndirectRefs(indirect), 2403 ...expectedIndirectRefs(indirect2), 2404 ], 2405 symbolDisplayString: "(alias) const foo: 1\nimport foo", 2406 } 2407 }); 2408 }); 2409 2410 it("disables looking into the child project if disableReferencedProjectLoad is set", () => { 2411 const ownMain: File = { 2412 path: `${tscWatch.projectRoot}/own/main.ts`, 2413 content: fileResolvingToMainDts.content 2414 }; 2415 const expectedProjectsOnOpen: VerifyProjects = { 2416 configuredProjects: [ 2417 { projectName: tsconfigPath, files: [tsconfigPath, ownMain.path, main.path, libFile.path, helper.path] }, 2418 ], 2419 inferredProjects: emptyArray 2420 }; 2421 verifyDisableReferencedProjectLoad({ 2422 solutionFiles: [`./own/main.ts`], 2423 solutionOptions: { 2424 outDir: "./target/", 2425 baseUrl: "./src/", 2426 disableReferencedProjectLoad: true 2427 }, 2428 configRefs: ["./tsconfig-src.json"], 2429 additionalFiles: [ownMain], 2430 expectedOpenEvents: [ 2431 ...expectedSolutionLoadAndTelemetry(), 2432 configFileDiagEvent(main.path, tsconfigPath, []) 2433 ], 2434 expectedDefaultProject: service => service.configuredProjects.get(tsconfigPath)!, 2435 expectedDefaultConfiguredProject: returnUndefined, 2436 expectedProjectsOnOpen, 2437 expectedReloadEvents: expectedReloadEvent(tsconfigPath) 2438 }); 2439 }); 2440 2441 it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => { 2442 const ownMain: File = { 2443 path: `${tscWatch.projectRoot}/own/main.ts`, 2444 content: `import { bar } from 'main'; 2445bar;` 2446 }; 2447 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 2448 const expectedProjectsOnOpen: VerifyProjects = { 2449 configuredProjects: [ 2450 { projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] }, 2451 { projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] }, 2452 ], 2453 inferredProjects: emptyArray 2454 }; 2455 verifyDisableReferencedProjectLoad({ 2456 solutionFiles: [`./own/main.ts`], 2457 solutionOptions: { 2458 outDir: "./target/", 2459 baseUrl: "./indirect1/", 2460 }, 2461 configRefs: ["./tsconfig-indirect1.json"], 2462 additionalFiles: [tsconfigIndirect, indirect, ownMain], 2463 expectedOpenEvents: [ 2464 ...expectedSolutionLoadAndTelemetry(), 2465 ...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path), 2466 configFileDiagEvent(main.path, tsconfigPath, []) 2467 ], 2468 expectedDefaultProject: service => service.configuredProjects.get(tsconfigPath)!, 2469 expectedDefaultConfiguredProject: returnUndefined, 2470 expectedProjectsOnOpen, 2471 expectedReloadEvents: [ 2472 ...expectedReloadEvent(tsconfigPath), 2473 ...expectedReloadEvent(tsconfigIndirect.path), 2474 ] 2475 }); 2476 }); 2477 2478 it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => { 2479 const ownMain: File = { 2480 path: `${tscWatch.projectRoot}/own/main.ts`, 2481 content: `import { bar } from 'main'; 2482bar;` 2483 }; 2484 const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true }); 2485 const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2"); 2486 const expectedProjectsOnOpen: VerifyProjects = { 2487 configuredProjects: [ 2488 { projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] }, 2489 { projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] }, 2490 ], 2491 inferredProjects: emptyArray 2492 }; 2493 verifyDisableReferencedProjectLoad({ 2494 solutionFiles: [`./own/main.ts`], 2495 solutionOptions: { 2496 outDir: "./target/", 2497 baseUrl: "./indirect1/", 2498 }, 2499 configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"], 2500 additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain], 2501 expectedOpenEvents: [ 2502 ...expectedSolutionLoadAndTelemetry(), 2503 ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath), 2504 configFileDiagEvent(main.path, tsconfigSrcPath, []) 2505 ], 2506 expectedDefaultProject: service => service.configuredProjects.get(tsconfigSrcPath)!, 2507 expectedDefaultConfiguredProject: service => service.configuredProjects.get(tsconfigSrcPath)!, 2508 expectedProjectsOnOpen, 2509 expectedReloadEvents: [ 2510 ...expectedReloadEvent(tsconfigPath), 2511 ...expectedReloadEvent(tsconfigSrcPath), 2512 ] 2513 }); 2514 }); 2515 }); 2516 }); 2517 2518 describe("auto import with referenced project", () => { 2519 function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) { 2520 const solnConfig: File = { 2521 path: `${tscWatch.projectRoot}/tsconfig.json`, 2522 content: JSON.stringify({ 2523 files: [], 2524 references: [ 2525 { path: "shared/src/library" }, 2526 { path: "app/src/program" } 2527 ] 2528 }) 2529 }; 2530 const sharedConfig: File = { 2531 path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`, 2532 content: JSON.stringify({ 2533 compilerOptions: { 2534 composite: true, 2535 outDir: "../../bld/library" 2536 } 2537 }) 2538 }; 2539 const sharedIndex: File = { 2540 path: `${tscWatch.projectRoot}/shared/src/library/index.ts`, 2541 content: `export function foo() {}` 2542 }; 2543 const sharedPackage: File = { 2544 path: `${tscWatch.projectRoot}/shared/package.json`, 2545 content: JSON.stringify({ 2546 name: "shared", 2547 version: "1.0.0", 2548 main: "bld/library/index.js", 2549 types: "bld/library/index.d.ts" 2550 }) 2551 }; 2552 const appConfig: File = { 2553 path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`, 2554 content: JSON.stringify({ 2555 compilerOptions: { 2556 composite: true, 2557 outDir: "../../bld/program", 2558 disableSourceOfProjectReferenceRedirect 2559 }, 2560 references: [ 2561 { path: "../../../shared/src/library" } 2562 ] 2563 }) 2564 }; 2565 const appBar: File = { 2566 path: `${tscWatch.projectRoot}/app/src/program/bar.ts`, 2567 content: `import {foo} from "shared";` 2568 }; 2569 const appIndex: File = { 2570 path: `${tscWatch.projectRoot}/app/src/program/index.ts`, 2571 content: `foo` 2572 }; 2573 const sharedSymlink: SymLink = { 2574 path: `${tscWatch.projectRoot}/node_modules/shared`, 2575 symLink: `${tscWatch.projectRoot}/shared` 2576 }; 2577 const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile]; 2578 const host = createServerHost(files); 2579 if (built) { 2580 const solutionBuilder = tscWatch.createSolutionBuilder(host, [solnConfig.path], {}); 2581 solutionBuilder.build(); 2582 host.clearOutput(); 2583 } 2584 const session = createSession(host); 2585 openFilesForSession([appIndex], session); 2586 const response = session.executeCommandSeq<protocol.CodeFixRequest>({ 2587 command: protocol.CommandTypes.GetCodeFixes, 2588 arguments: { 2589 file: appIndex.path, 2590 startLine: 1, 2591 startOffset: 1, 2592 endLine: 1, 2593 endOffset: 4, 2594 errorCodes: [Diagnostics.Cannot_find_name_0.code], 2595 } 2596 }).response as protocol.CodeFixAction[]; 2597 assert.deepEqual(response, [ 2598 { 2599 fixName: "import", 2600 description: `Import 'foo' from module "shared"`, 2601 changes: [{ 2602 fileName: appIndex.path, 2603 textChanges: [{ 2604 start: { line: 1, offset: 1 }, 2605 end: { line: 1, offset: 1 }, 2606 newText: 'import { foo } from "shared";\n\n', 2607 }], 2608 }], 2609 commands: undefined, 2610 fixAllDescription: undefined, 2611 fixId: undefined 2612 } 2613 ]); 2614 } 2615 2616 it("when project is built", () => { 2617 verifyAutoImport(/*built*/ true); 2618 }); 2619 it("when project is not built", () => { 2620 verifyAutoImport(/*built*/ false); 2621 }); 2622 it("when disableSourceOfProjectReferenceRedirect is true", () => { 2623 verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); 2624 }); 2625 }); 2626 2627 it("when files from two projects are open and one project references", () => { 2628 function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] { 2629 const file: File = { 2630 path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`, 2631 content: `export const ${packageName}Const = 10;` 2632 }; 2633 const config: File = { 2634 path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`, 2635 content: JSON.stringify({ 2636 compilerOptions: { composite: true, ...optionsToExtend || {} }, 2637 references: references?.map(path => ({ path: `../${path}` })) 2638 }) 2639 }; 2640 return [file, config]; 2641 } 2642 const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]); 2643 const [coreFile, coreConfig] = getPackageAndFile("core"); 2644 const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1"); 2645 const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]); 2646 const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]); 2647 const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true }); 2648 const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]); 2649 const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true }); 2650 const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]); 2651 const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]); 2652 const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]); 2653 const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2"); 2654 2655 const host = createServerHost([ 2656 libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config, 2657 indirectFile, indirectConfig, coreRef1File, coreRef1Config, 2658 indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config, 2659 indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config, 2660 refToCoreRef3File, refToCoreRef3Config, 2661 indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config 2662 ], { useCaseSensitiveFileNames: true }); 2663 const session = createSession(host); 2664 const service = session.getProjectService(); 2665 openFilesForSession([mainFile, coreFile], session); 2666 2667 verifyProject(mainConfig); 2668 verifyProject(coreConfig); 2669 2670 // Find all refs in coreFile 2671 session.executeCommandSeq<protocol.ReferencesRequest>({ 2672 command: protocol.CommandTypes.References, 2673 arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`) 2674 }); 2675 verifyProject(mainConfig); 2676 verifyProject(coreConfig); 2677 verifyNoProject(noCoreRef1Config); // Should not be loaded 2678 verifyProject(indirectConfig); 2679 verifyProject(coreRef1Config); 2680 verifyProject(indirectDisabledChildLoad1Config); 2681 verifyNoProject(coreRef2Config); // Should not be loaded 2682 verifyProject(indirectDisabledChildLoad2Config); 2683 verifyProject(coreRef3Config); 2684 verifyProject(refToCoreRef3Config); 2685 verifyNoProject(indirectNoCoreRefConfig); // Should not be loaded 2686 verifyNoProject(noCoreRef2Config); // Should not be loaded 2687 2688 function verifyProject(config: File) { 2689 assert.isDefined(service.configuredProjects.get(config.path), `Expected to find ${config.path}`); 2690 } 2691 2692 function verifyNoProject(config: File) { 2693 assert.isUndefined(service.configuredProjects.get(config.path), `Expected to not find ${config.path}`); 2694 } 2695 }); 2696 }); 2697} 2698