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