1namespace ts.projectSystem { 2 function documentSpanFromSubstring({ file, text, contextText, options, contextOptions }: DocumentSpanFromSubstring): DocumentSpan { 3 const contextSpan = contextText !== undefined ? documentSpanFromSubstring({ file, text: contextText, options: contextOptions }) : undefined; 4 return { 5 fileName: file.path, 6 textSpan: textSpanFromSubstring(file.content, text, options), 7 ...contextSpan && { contextSpan: contextSpan.textSpan } 8 }; 9 } 10 11 function renameLocation(input: DocumentSpanFromSubstring): RenameLocation { 12 return documentSpanFromSubstring(input); 13 } 14 15 interface MakeReferenceEntry extends DocumentSpanFromSubstring { 16 isDefinition: boolean; 17 } 18 function makeReferenceEntry({ isDefinition, ...rest }: MakeReferenceEntry): ReferenceEntry { 19 return { 20 ...documentSpanFromSubstring(rest), 21 isDefinition, 22 isWriteAccess: isDefinition, 23 isInString: undefined, 24 }; 25 } 26 27 function checkDeclarationFiles(file: File, session: TestSession, expectedFiles: readonly File[]): void { 28 openFilesForSession([file], session); 29 const project = Debug.checkDefined(session.getProjectService().getDefaultProjectForFile(file.path as server.NormalizedPath, /*ensureProject*/ false)); 30 const program = project.getCurrentProgram()!; 31 const output = getFileEmitOutput(program, Debug.checkDefined(program.getSourceFile(file.path)), /*emitOnlyDtsFiles*/ true); 32 closeFilesForSession([file], session); 33 34 Debug.assert(!output.emitSkipped); 35 assert.deepEqual(output.outputFiles, expectedFiles.map((e): OutputFile => ({ name: e.path, text: e.content, writeByteOrderMark: false }))); 36 } 37 38 describe("unittests:: tsserver:: with declaration file maps:: project references", () => { 39 const aTs: File = { 40 path: "/a/a.ts", 41 content: "export function fnA() {}\nexport interface IfaceA {}\nexport const instanceA: IfaceA = {};", 42 }; 43 const compilerOptions: CompilerOptions = { 44 outDir: "bin", 45 declaration: true, 46 declarationMap: true, 47 composite: true, 48 }; 49 const configContent = JSON.stringify({ compilerOptions }); 50 const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; 51 52 const aDtsMapContent: RawSourceMap = { 53 version: 3, 54 file: "a.d.ts", 55 sourceRoot: "", 56 sources: ["../a.ts"], 57 names: [], 58 mappings: "AAAA,wBAAgB,GAAG,SAAK;AACxB,MAAM,WAAW,MAAM;CAAG;AAC1B,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC" 59 }; 60 const aDtsMap: File = { 61 path: "/a/bin/a.d.ts.map", 62 content: JSON.stringify(aDtsMapContent), 63 }; 64 const aDts: File = { 65 path: "/a/bin/a.d.ts", 66 // ${""} is needed to mangle the sourceMappingURL part or it breaks the build 67 content: `export declare function fnA(): void;\nexport interface IfaceA {\n}\nexport declare const instanceA: IfaceA;\n//# source${""}MappingURL=a.d.ts.map`, 68 }; 69 70 const bTs: File = { 71 path: "/b/b.ts", 72 content: "export function fnB() {}", 73 }; 74 const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; 75 76 const bDtsMapContent: RawSourceMap = { 77 version: 3, 78 file: "b.d.ts", 79 sourceRoot: "", 80 sources: ["../b.ts"], 81 names: [], 82 mappings: "AAAA,wBAAgB,GAAG,SAAK", 83 }; 84 const bDtsMap: File = { 85 path: "/b/bin/b.d.ts.map", 86 content: JSON.stringify(bDtsMapContent), 87 }; 88 const bDts: File = { 89 // ${""} is need to mangle the sourceMappingURL part so it doesn't break the build 90 path: "/b/bin/b.d.ts", 91 content: `export declare function fnB(): void;\n//# source${""}MappingURL=b.d.ts.map`, 92 }; 93 94 const dummyFile: File = { 95 path: "/dummy/dummy.ts", 96 content: "let a = 10;" 97 }; 98 99 const userTs: File = { 100 path: "/user/user.ts", 101 content: 'import * as a from "../a/bin/a";\nimport * as b from "../b/bin/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', 102 }; 103 104 const userTsForConfigProject: File = { 105 path: "/user/user.ts", 106 content: 'import * as a from "../a/a";\nimport * as b from "../b/b";\nexport function fnUser() { a.fnA(); b.fnB(); a.instanceA; }', 107 }; 108 109 const userTsconfig: File = { 110 path: "/user/tsconfig.json", 111 content: JSON.stringify({ 112 file: ["user.ts"], 113 references: [{ path: "../a" }, { path: "../b" }] 114 }) 115 }; 116 117 function makeSampleProjects(addUserTsConfig?: boolean, keepAllFiles?: boolean) { 118 const host = createServerHost([aTs, aTsconfig, aDtsMap, aDts, bTsconfig, bTs, bDtsMap, bDts, ...(addUserTsConfig ? [userTsForConfigProject, userTsconfig] : [userTs]), dummyFile]); 119 const session = createSession(host); 120 121 checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); 122 checkDeclarationFiles(bTs, session, [bDtsMap, bDts]); 123 124 // Testing what happens if we delete the original sources. 125 if (!keepAllFiles) { 126 host.deleteFile(bTs.path); 127 } 128 129 openFilesForSession([userTs], session); 130 const service = session.getProjectService(); 131 // If config file then userConfig project and bConfig project since it is referenced 132 checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 }); 133 return session; 134 } 135 136 function verifyInferredProjectUnchanged(session: TestSession) { 137 checkProjectActualFiles(session.getProjectService().inferredProjects[0], [userTs.path, aDts.path, bDts.path]); 138 } 139 140 function verifyDummyProject(session: TestSession) { 141 checkProjectActualFiles(session.getProjectService().inferredProjects[0], [dummyFile.path]); 142 } 143 144 function verifyOnlyOrphanInferredProject(session: TestSession) { 145 openFilesForSession([dummyFile], session); 146 checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); 147 verifyDummyProject(session); 148 } 149 150 function verifySingleInferredProject(session: TestSession) { 151 checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1 }); 152 verifyInferredProjectUnchanged(session); 153 154 // Close user file should close all the projects after opening dummy file 155 closeFilesForSession([userTs], session); 156 verifyOnlyOrphanInferredProject(session); 157 } 158 159 function verifyATsConfigProject(session: TestSession) { 160 checkProjectActualFiles(session.getProjectService().configuredProjects.get(aTsconfig.path)!, [aTs.path, aTsconfig.path]); 161 } 162 163 function verifyATsConfigOriginalProject(session: TestSession) { 164 checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); 165 verifyInferredProjectUnchanged(session); 166 verifyATsConfigProject(session); 167 // Close user file should close all the projects 168 closeFilesForSession([userTs], session); 169 verifyOnlyOrphanInferredProject(session); 170 } 171 172 function verifyATsConfigWhenOpened(session: TestSession) { 173 checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); 174 verifyInferredProjectUnchanged(session); 175 verifyATsConfigProject(session); 176 177 closeFilesForSession([userTs], session); 178 openFilesForSession([dummyFile], session); 179 checkNumberOfProjects(session.getProjectService(), { inferredProjects: 1, configuredProjects: 1 }); 180 verifyDummyProject(session); 181 verifyATsConfigProject(session); // ATsConfig should still be alive 182 } 183 184 function verifyUserTsConfigProject(session: TestSession) { 185 checkProjectActualFiles(session.getProjectService().configuredProjects.get(userTsconfig.path)!, [userTs.path, aTs.path, userTsconfig.path]); 186 } 187 188 it("goToDefinition", () => { 189 const session = makeSampleProjects(); 190 const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, protocol.CommandTypes.Definition, protocolFileLocationFromSubstring(userTs, "fnA()")); 191 assert.deepEqual(response, [ 192 protocolFileSpanWithContextFromSubstring({ 193 file: aTs, 194 text: "fnA", 195 contextText: "export function fnA() {}" 196 }) 197 ]); 198 verifySingleInferredProject(session); 199 }); 200 201 it("getDefinitionAndBoundSpan", () => { 202 const session = makeSampleProjects(); 203 const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); 204 assert.deepEqual(response, { 205 textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), 206 definitions: [ 207 protocolFileSpanWithContextFromSubstring({ 208 file: aTs, 209 text: "fnA", 210 contextText: "export function fnA() {}" 211 }) 212 ], 213 }); 214 verifySingleInferredProject(session); 215 }); 216 217 it("getDefinitionAndBoundSpan with file navigation", () => { 218 const session = makeSampleProjects(/*addUserTsConfig*/ true); 219 const response = executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>(session, protocol.CommandTypes.DefinitionAndBoundSpan, protocolFileLocationFromSubstring(userTs, "fnA()")); 220 assert.deepEqual(response, { 221 textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), 222 definitions: [ 223 protocolFileSpanWithContextFromSubstring({ 224 file: aTs, 225 text: "fnA", 226 contextText: "export function fnA() {}" 227 }) 228 ], 229 }); 230 checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); 231 verifyUserTsConfigProject(session); 232 233 // Navigate to the definition 234 closeFilesForSession([userTs], session); 235 openFilesForSession([aTs], session); 236 237 // UserTs configured project should be alive 238 checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); 239 verifyUserTsConfigProject(session); 240 verifyATsConfigProject(session); 241 242 closeFilesForSession([aTs], session); 243 verifyOnlyOrphanInferredProject(session); 244 }); 245 246 it("goToType", () => { 247 const session = makeSampleProjects(); 248 const response = executeSessionRequest<protocol.TypeDefinitionRequest, protocol.TypeDefinitionResponse>(session, protocol.CommandTypes.TypeDefinition, protocolFileLocationFromSubstring(userTs, "instanceA")); 249 assert.deepEqual(response, [ 250 protocolFileSpanWithContextFromSubstring({ 251 file: aTs, 252 text: "IfaceA", 253 contextText: "export interface IfaceA {}" 254 }) 255 ]); 256 verifySingleInferredProject(session); 257 }); 258 259 it("goToImplementation", () => { 260 const session = makeSampleProjects(); 261 const response = executeSessionRequest<protocol.ImplementationRequest, protocol.ImplementationResponse>(session, protocol.CommandTypes.Implementation, protocolFileLocationFromSubstring(userTs, "fnA()")); 262 assert.deepEqual(response, [ 263 protocolFileSpanWithContextFromSubstring({ 264 file: aTs, 265 text: "fnA", 266 contextText: "export function fnA() {}" 267 })]); 268 verifySingleInferredProject(session); 269 }); 270 271 it("goToDefinition -- target does not exist", () => { 272 const session = makeSampleProjects(); 273 const response = executeSessionRequest<protocol.DefinitionRequest, protocol.DefinitionResponse>(session, CommandNames.Definition, protocolFileLocationFromSubstring(userTs, "fnB()")); 274 // bTs does not exist, so stick with bDts 275 assert.deepEqual(response, [ 276 protocolFileSpanWithContextFromSubstring({ 277 file: bDts, 278 text: "fnB", 279 contextText: "export declare function fnB(): void;" 280 }) 281 ]); 282 verifySingleInferredProject(session); 283 }); 284 285 it("navigateTo", () => { 286 const session = makeSampleProjects(); 287 const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: userTs.path, searchValue: "fn" }); 288 assert.deepEqual<readonly protocol.NavtoItem[] | undefined>(response, [ 289 { 290 ...protocolFileSpanFromSubstring({ 291 file: bDts, 292 text: "export declare function fnB(): void;" 293 }), 294 name: "fnB", 295 matchKind: "prefix", 296 isCaseSensitive: true, 297 kind: ScriptElementKind.functionElement, 298 kindModifiers: "export,declare", 299 }, 300 { 301 ...protocolFileSpanFromSubstring({ 302 file: userTs, 303 text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" 304 }), 305 name: "fnUser", 306 matchKind: "prefix", 307 isCaseSensitive: true, 308 kind: ScriptElementKind.functionElement, 309 kindModifiers: "export", 310 }, 311 { 312 ...protocolFileSpanFromSubstring({ 313 file: aTs, 314 text: "export function fnA() {}" 315 }), 316 name: "fnA", 317 matchKind: "prefix", 318 isCaseSensitive: true, 319 kind: ScriptElementKind.functionElement, 320 kindModifiers: "export", 321 }, 322 ]); 323 324 verifyATsConfigOriginalProject(session); 325 }); 326 327 it("navigateToAll -- when neither file nor project is specified", () => { 328 const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); 329 const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { file: undefined, searchValue: "fn" }); 330 assert.deepEqual<readonly protocol.NavtoItem[] | undefined>(response, [ 331 { 332 ...protocolFileSpanFromSubstring({ 333 file: bTs, 334 text: "export function fnB() {}" 335 }), 336 name: "fnB", 337 matchKind: "prefix", 338 isCaseSensitive: true, 339 kind: ScriptElementKind.functionElement, 340 kindModifiers: "export", 341 }, 342 { 343 ...protocolFileSpanFromSubstring({ 344 file: aTs, 345 text: "export function fnA() {}" 346 }), 347 name: "fnA", 348 matchKind: "prefix", 349 isCaseSensitive: true, 350 kind: ScriptElementKind.functionElement, 351 kindModifiers: "export", 352 }, 353 { 354 ...protocolFileSpanFromSubstring({ 355 file: userTs, 356 text: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" 357 }), 358 name: "fnUser", 359 matchKind: "prefix", 360 isCaseSensitive: true, 361 kind: ScriptElementKind.functionElement, 362 kindModifiers: "export", 363 } 364 ]); 365 }); 366 367 it("navigateToAll -- when file is not specified but project is", () => { 368 const session = makeSampleProjects(/*addUserTsConfig*/ true, /*keepAllFiles*/ true); 369 const response = executeSessionRequest<protocol.NavtoRequest, protocol.NavtoResponse>(session, CommandNames.Navto, { projectFileName: bTsconfig.path, file: undefined, searchValue: "fn" }); 370 assert.deepEqual<readonly protocol.NavtoItem[] | undefined>(response, [ 371 { 372 ...protocolFileSpanFromSubstring({ 373 file: bTs, 374 text: "export function fnB() {}" 375 }), 376 name: "fnB", 377 matchKind: "prefix", 378 isCaseSensitive: true, 379 kind: ScriptElementKind.functionElement, 380 kindModifiers: "export", 381 } 382 ]); 383 }); 384 385 const referenceATs = (aTs: File): protocol.ReferencesResponseItem => makeReferenceItem({ 386 file: aTs, 387 isDefinition: true, 388 text: "fnA", 389 contextText: "export function fnA() {}", 390 lineText: "export function fnA() {}" 391 }); 392 const referencesUserTs = (userTs: File): readonly protocol.ReferencesResponseItem[] => [ 393 makeReferenceItem({ 394 file: userTs, 395 isDefinition: false, 396 text: "fnA", 397 lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" 398 }), 399 ]; 400 401 it("findAllReferences", () => { 402 const session = makeSampleProjects(); 403 404 const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnA()")); 405 assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, { 406 refs: [...referencesUserTs(userTs), referenceATs(aTs)], 407 symbolName: "fnA", 408 symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnA()").offset, 409 symbolDisplayString: "function fnA(): void", 410 }); 411 412 verifyATsConfigOriginalProject(session); 413 }); 414 415 it("findAllReferences -- starting at definition", () => { 416 const session = makeSampleProjects(); 417 openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. 418 const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(aTs, "fnA")); 419 assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, { 420 refs: [referenceATs(aTs), ...referencesUserTs(userTs)], 421 symbolName: "fnA", 422 symbolStartOffset: protocolLocationFromSubstring(aTs.content, "fnA").offset, 423 symbolDisplayString: "function fnA(): void", 424 }); 425 verifyATsConfigWhenOpened(session); 426 }); 427 428 interface ReferencesFullRequest extends protocol.FileLocationRequest { readonly command: protocol.CommandTypes.ReferencesFull; } 429 interface ReferencesFullResponse extends protocol.Response { readonly body: readonly ReferencedSymbol[]; } 430 431 it("findAllReferencesFull", () => { 432 const session = makeSampleProjects(); 433 434 const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(userTs, "fnA()")); 435 436 assert.deepEqual<readonly ReferencedSymbol[]>(responseFull, [ 437 { 438 definition: { 439 ...documentSpanFromSubstring({ 440 file: aTs, 441 text: "fnA", 442 contextText: "export function fnA() {}" 443 }), 444 kind: ScriptElementKind.functionElement, 445 name: "function fnA(): void", 446 containerKind: ScriptElementKind.unknown, 447 containerName: "", 448 displayParts: [ 449 keywordPart(SyntaxKind.FunctionKeyword), 450 spacePart(), 451 displayPart("fnA", SymbolDisplayPartKind.functionName), 452 punctuationPart(SyntaxKind.OpenParenToken), 453 punctuationPart(SyntaxKind.CloseParenToken), 454 punctuationPart(SyntaxKind.ColonToken), 455 spacePart(), 456 keywordPart(SyntaxKind.VoidKeyword), 457 ], 458 }, 459 references: [ 460 makeReferenceEntry({ file: userTs, /*isDefinition*/ isDefinition: false, text: "fnA" }), 461 makeReferenceEntry({ file: aTs, /*isDefinition*/ isDefinition: true, text: "fnA", contextText: "export function fnA() {}" }), 462 ], 463 }, 464 ]); 465 verifyATsConfigOriginalProject(session); 466 }); 467 468 it("findAllReferencesFull definition is in mapped file", () => { 469 const aTs: File = { path: "/a/a.ts", content: `function f() {}` }; 470 const aTsconfig: File = { 471 path: "/a/tsconfig.json", 472 content: JSON.stringify({ compilerOptions: { declaration: true, declarationMap: true, outFile: "../bin/a.js" } }), 473 }; 474 const bTs: File = { path: "/b/b.ts", content: `f();` }; 475 const bTsconfig: File = { path: "/b/tsconfig.json", content: JSON.stringify({ references: [{ path: "../a" }] }) }; 476 const aDts: File = { path: "/bin/a.d.ts", content: `declare function f(): void;\n//# sourceMappingURL=a.d.ts.map` }; 477 const aDtsMap: File = { 478 path: "/bin/a.d.ts.map", 479 content: JSON.stringify({ version: 3, file: "a.d.ts", sourceRoot: "", sources: ["../a/a.ts"], names: [], mappings: "AAAA,iBAAS,CAAC,SAAK" }), 480 }; 481 482 const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap])); 483 checkDeclarationFiles(aTs, session, [aDtsMap, aDts]); 484 openFilesForSession([bTs], session); 485 checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b 486 487 const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()")); 488 489 assert.deepEqual<readonly ReferencedSymbol[]>(responseFull, [ 490 { 491 definition: { 492 ...documentSpanFromSubstring({ 493 file: aTs, 494 text: "f", 495 options: { index: 1 }, 496 contextText: "function f() {}" 497 }), 498 containerKind: ScriptElementKind.unknown, 499 containerName: "", 500 displayParts: [ 501 keywordPart(SyntaxKind.FunctionKeyword), 502 spacePart(), 503 displayPart("f", SymbolDisplayPartKind.functionName), 504 punctuationPart(SyntaxKind.OpenParenToken), 505 punctuationPart(SyntaxKind.CloseParenToken), 506 punctuationPart(SyntaxKind.ColonToken), 507 spacePart(), 508 keywordPart(SyntaxKind.VoidKeyword), 509 ], 510 kind: ScriptElementKind.functionElement, 511 name: "function f(): void", 512 }, 513 references: [ 514 makeReferenceEntry({ 515 file: aTs, 516 text: "f", 517 options: { index: 1 }, 518 contextText: "function f() {}", 519 isDefinition: true 520 }), 521 { 522 fileName: bTs.path, 523 isDefinition: false, 524 isInString: undefined, 525 isWriteAccess: false, 526 textSpan: { start: 0, length: 1 }, 527 }, 528 ], 529 } 530 ]); 531 }); 532 533 it("findAllReferences -- target does not exist", () => { 534 const session = makeSampleProjects(); 535 536 const response = executeSessionRequest<protocol.ReferencesRequest, protocol.ReferencesResponse>(session, protocol.CommandTypes.References, protocolFileLocationFromSubstring(userTs, "fnB()")); 537 assert.deepEqual<protocol.ReferencesResponseBody | undefined>(response, { 538 refs: [ 539 makeReferenceItem({ 540 file: bDts, 541 isDefinition: true, 542 text: "fnB", 543 contextText: "export declare function fnB(): void;", 544 lineText: "export declare function fnB(): void;" 545 }), 546 makeReferenceItem({ 547 file: userTs, 548 isDefinition: false, 549 text: "fnB", 550 lineText: "export function fnUser() { a.fnA(); b.fnB(); a.instanceA; }" 551 }), 552 ], 553 symbolName: "fnB", 554 symbolStartOffset: protocolLocationFromSubstring(userTs.content, "fnB()").offset, 555 symbolDisplayString: "function fnB(): void", 556 }); 557 verifySingleInferredProject(session); 558 }); 559 560 const renameATs = (aTs: File): protocol.SpanGroup => ({ 561 file: aTs.path, 562 locs: [ 563 protocolRenameSpanFromSubstring({ 564 fileText: aTs.content, 565 text: "fnA", 566 contextText: "export function fnA() {}" 567 }) 568 ], 569 }); 570 const renameUserTs = (userTs: File): protocol.SpanGroup => ({ 571 file: userTs.path, 572 locs: [ 573 protocolRenameSpanFromSubstring({ 574 fileText: userTs.content, 575 text: "fnA" 576 }) 577 ], 578 }); 579 580 it("renameLocations", () => { 581 const session = makeSampleProjects(); 582 const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnA()")); 583 assert.deepEqual<protocol.RenameResponseBody | undefined>(response, { 584 info: { 585 canRename: true, 586 displayName: "fnA", 587 fileToRename: undefined, 588 fullDisplayName: '"/a/bin/a".fnA', // Ideally this would use the original source's path instead of the declaration file's path. 589 kind: ScriptElementKind.functionElement, 590 kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), 591 triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), 592 }, 593 locs: [renameUserTs(userTs), renameATs(aTs)], 594 }); 595 verifyATsConfigOriginalProject(session); 596 }); 597 598 it("renameLocations -- starting at definition", () => { 599 const session = makeSampleProjects(); 600 openFilesForSession([aTs], session); // If it's not opened, the reference isn't found. 601 const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "fnA")); 602 assert.deepEqual<protocol.RenameResponseBody | undefined>(response, { 603 info: { 604 canRename: true, 605 displayName: "fnA", 606 fileToRename: undefined, 607 fullDisplayName: '"/a/a".fnA', 608 kind: ScriptElementKind.functionElement, 609 kindModifiers: ScriptElementKindModifier.exportedModifier, 610 triggerSpan: protocolTextSpanFromSubstring(aTs.content, "fnA"), 611 }, 612 locs: [renameATs(aTs), renameUserTs(userTs)], 613 }); 614 verifyATsConfigWhenOpened(session); 615 }); 616 617 it("renameLocationsFull", () => { 618 const session = makeSampleProjects(); 619 const response = executeSessionRequest<protocol.RenameFullRequest, protocol.RenameFullResponse>(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); 620 assert.deepEqual<readonly RenameLocation[]>(response, [ 621 renameLocation({ file: userTs, text: "fnA" }), 622 renameLocation({ file: aTs, text: "fnA", contextText: "export function fnA() {}" }), 623 ]); 624 verifyATsConfigOriginalProject(session); 625 }); 626 627 it("renameLocations -- target does not exist", () => { 628 const session = makeSampleProjects(); 629 const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(userTs, "fnB()")); 630 assert.deepEqual<protocol.RenameResponseBody | undefined>(response, { 631 info: { 632 canRename: true, 633 displayName: "fnB", 634 fileToRename: undefined, 635 fullDisplayName: '"/b/bin/b".fnB', 636 kind: ScriptElementKind.functionElement, 637 kindModifiers: [ScriptElementKindModifier.exportedModifier, ScriptElementKindModifier.ambientModifier].join(","), 638 triggerSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), 639 }, 640 locs: [ 641 { 642 file: bDts.path, 643 locs: [ 644 protocolRenameSpanFromSubstring({ 645 fileText: bDts.content, 646 text: "fnB", 647 contextText: "export declare function fnB(): void;" 648 }) 649 ], 650 }, 651 { 652 file: userTs.path, 653 locs: [ 654 protocolRenameSpanFromSubstring({ 655 fileText: userTs.content, 656 text: "fnB" 657 }) 658 ], 659 }, 660 ], 661 }); 662 verifySingleInferredProject(session); 663 }); 664 665 it("getEditsForFileRename", () => { 666 const session = makeSampleProjects(); 667 const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, protocol.CommandTypes.GetEditsForFileRename, { 668 oldFilePath: aTs.path, 669 newFilePath: "/a/aNew.ts", 670 }); 671 assert.deepEqual<readonly protocol.FileCodeEdits[]>(response, [ 672 { 673 fileName: userTs.path, 674 textChanges: [ 675 { ...protocolTextSpanFromSubstring(userTs.content, "../a/bin/a"), newText: "../a/bin/aNew" }, 676 ], 677 }, 678 ]); 679 verifySingleInferredProject(session); 680 }); 681 682 it("getEditsForFileRename when referencing project doesnt include file and its renamed", () => { 683 const aTs: File = { path: "/a/src/a.ts", content: "" }; 684 const aTsconfig: File = { 685 path: "/a/tsconfig.json", 686 content: JSON.stringify({ 687 compilerOptions: { 688 composite: true, 689 declaration: true, 690 declarationMap: true, 691 outDir: "./build", 692 } 693 }), 694 }; 695 const bTs: File = { path: "/b/src/b.ts", content: "" }; 696 const bTsconfig: File = { 697 path: "/b/tsconfig.json", 698 content: JSON.stringify({ 699 compilerOptions: { 700 composite: true, 701 outDir: "./build", 702 }, 703 include: ["./src"], 704 references: [{ path: "../a" }], 705 }), 706 }; 707 708 const host = createServerHost([aTs, aTsconfig, bTs, bTsconfig]); 709 const session = createSession(host); 710 openFilesForSession([aTs, bTs], session); 711 const response = executeSessionRequest<protocol.GetEditsForFileRenameRequest, protocol.GetEditsForFileRenameResponse>(session, CommandNames.GetEditsForFileRename, { 712 oldFilePath: aTs.path, 713 newFilePath: "/a/src/a1.ts", 714 }); 715 assert.deepEqual<readonly protocol.FileCodeEdits[]>(response, []); // Should not change anything 716 }); 717 718 it("does not jump to source if inlined sources", () => { 719 const aDtsInlinedSources: RawSourceMap = { 720 ...aDtsMapContent, 721 sourcesContent: [aTs.content] 722 }; 723 const aDtsMapInlinedSources: File = { 724 path: aDtsMap.path, 725 content: JSON.stringify(aDtsInlinedSources) 726 }; 727 const host = createServerHost([aTs, aDtsMapInlinedSources, aDts, bTs, bDtsMap, bDts, userTs, dummyFile]); 728 const session = createSession(host); 729 730 openFilesForSession([userTs], session); 731 const service = session.getProjectService(); 732 // If config file then userConfig project and bConfig project since it is referenced 733 checkNumberOfProjects(service, { inferredProjects: 1 }); 734 735 // Inlined so does not jump to aTs 736 assert.deepEqual( 737 executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>( 738 session, 739 protocol.CommandTypes.DefinitionAndBoundSpan, 740 protocolFileLocationFromSubstring(userTs, "fnA()") 741 ), 742 { 743 textSpan: protocolTextSpanFromSubstring(userTs.content, "fnA"), 744 definitions: [ 745 protocolFileSpanWithContextFromSubstring({ 746 file: aDts, 747 text: "fnA", 748 contextText: "export declare function fnA(): void;" 749 }) 750 ], 751 } 752 ); 753 754 // Not inlined, jumps to bTs 755 assert.deepEqual( 756 executeSessionRequest<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionAndBoundSpanResponse>( 757 session, 758 protocol.CommandTypes.DefinitionAndBoundSpan, 759 protocolFileLocationFromSubstring(userTs, "fnB()") 760 ), 761 { 762 textSpan: protocolTextSpanFromSubstring(userTs.content, "fnB"), 763 definitions: [ 764 protocolFileSpanWithContextFromSubstring({ 765 file: bTs, 766 text: "fnB", 767 contextText: "export function fnB() {}" 768 }) 769 ], 770 } 771 ); 772 773 verifySingleInferredProject(session); 774 }); 775 }); 776} 777