1import { expect } from "chai"; 2 3import * as ts from "../../_namespaces/ts"; 4import * as Harness from "../../_namespaces/Harness"; 5import * as Utils from "../../_namespaces/Utils"; 6 7let lastWrittenToHost: string; 8const noopFileWatcher: ts.FileWatcher = { close: ts.noop }; 9const mockHost: ts.server.ServerHost = { 10 args: [], 11 newLine: "\n", 12 useCaseSensitiveFileNames: true, 13 write(s): void { lastWrittenToHost = s; }, 14 readFile: ts.returnUndefined, 15 writeFile: ts.noop, 16 resolvePath(): string { return undefined!; }, // TODO: GH#18217 17 fileExists: () => false, 18 directoryExists: () => false, 19 getDirectories: () => [], 20 createDirectory: ts.noop, 21 getExecutingFilePath(): string { return ""; }, 22 getCurrentDirectory(): string { return ""; }, 23 getEnvironmentVariable(): string { return ""; }, 24 readDirectory() { return []; }, 25 exit: ts.noop, 26 setTimeout() { return 0; }, 27 clearTimeout: ts.noop, 28 setImmediate: () => 0, 29 clearImmediate: ts.noop, 30 createHash: Harness.mockHash, 31 watchFile: () => noopFileWatcher, 32 watchDirectory: () => noopFileWatcher 33}; 34 35class TestSession extends ts.server.Session { 36 getProjectService() { 37 return this.projectService; 38 } 39} 40 41describe("unittests:: tsserver:: Session:: General functionality", () => { 42 let session: TestSession; 43 let lastSent: ts.server.protocol.Message; 44 45 function createSession(): TestSession { 46 const opts: ts.server.SessionOptions = { 47 host: mockHost, 48 cancellationToken: ts.server.nullCancellationToken, 49 useSingleInferredProject: false, 50 useInferredProjectPerProjectRoot: false, 51 typingsInstaller: undefined!, // TODO: GH#18217 52 byteLength: Utils.byteLength, 53 hrtime: process.hrtime, 54 logger: ts.projectSystem.nullLogger(), 55 canUseEvents: true 56 }; 57 return new TestSession(opts); 58 } 59 60 // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test 61 let oldPrepare: ts.AnyFunction; 62 before(() => { 63 oldPrepare = (Error as any).prepareStackTrace; 64 delete (Error as any).prepareStackTrace; 65 }); 66 67 after(() => { 68 (Error as any).prepareStackTrace = oldPrepare; 69 }); 70 71 beforeEach(() => { 72 session = createSession(); 73 session.send = (msg: ts.server.protocol.Message) => { 74 lastSent = msg; 75 }; 76 }); 77 78 describe("executeCommand", () => { 79 it("should throw when commands are executed with invalid arguments", () => { 80 const req: ts.server.protocol.FileRequest = { 81 command: ts.server.CommandNames.Open, 82 seq: 0, 83 type: "request", 84 arguments: { 85 file: undefined! // TODO: GH#18217 86 } 87 }; 88 89 expect(() => session.executeCommand(req)).to.throw(); 90 }); 91 it("should output an error response when a command does not exist", () => { 92 const req: ts.server.protocol.Request = { 93 command: "foobar", 94 seq: 0, 95 type: "request" 96 }; 97 98 session.executeCommand(req); 99 100 const expected: ts.server.protocol.Response = { 101 command: ts.server.CommandNames.Unknown, 102 type: "response", 103 seq: 0, 104 message: "Unrecognized JSON command: foobar", 105 request_seq: 0, 106 success: false, 107 performanceData: undefined, 108 }; 109 expect(lastSent).to.deep.equal(expected); 110 }); 111 it("should return a tuple containing the response and if a response is required on success", () => { 112 const req: ts.server.protocol.ConfigureRequest = { 113 command: ts.server.CommandNames.Configure, 114 seq: 0, 115 type: "request", 116 arguments: { 117 hostInfo: "unit test", 118 formatOptions: { 119 newLineCharacter: "`n" 120 } 121 } 122 }; 123 124 expect(session.executeCommand(req)).to.deep.equal({ 125 responseRequired: false 126 }); 127 expect(lastSent).to.deep.equal({ 128 command: ts.server.CommandNames.Configure, 129 type: "response", 130 success: true, 131 request_seq: 0, 132 seq: 0, 133 body: undefined, 134 performanceData: undefined, 135 }); 136 }); 137 it("should handle literal types in request", () => { 138 const configureRequest: ts.server.protocol.ConfigureRequest = { 139 command: ts.server.CommandNames.Configure, 140 seq: 0, 141 type: "request", 142 arguments: { 143 formatOptions: { 144 indentStyle: ts.server.protocol.IndentStyle.Block, 145 } 146 } 147 }; 148 149 session.onMessage(JSON.stringify(configureRequest)); 150 151 assert.equal(session.getProjectService().getFormatCodeOptions("" as ts.server.NormalizedPath).indentStyle, ts.IndentStyle.Block); 152 153 const setOptionsRequest: ts.server.protocol.SetCompilerOptionsForInferredProjectsRequest = { 154 command: ts.server.CommandNames.CompilerOptionsForInferredProjects, 155 seq: 1, 156 type: "request", 157 arguments: { 158 options: { 159 module: ts.server.protocol.ModuleKind.System, 160 target: ts.server.protocol.ScriptTarget.ES5, 161 jsx: ts.server.protocol.JsxEmit.React, 162 newLine: ts.server.protocol.NewLineKind.Lf, 163 moduleResolution: ts.server.protocol.ModuleResolutionKind.Node, 164 } 165 } 166 }; 167 session.onMessage(JSON.stringify(setOptionsRequest)); 168 assert.deepEqual( 169 session.getProjectService().getCompilerOptionsForInferredProjects(), 170 { 171 module: ts.ModuleKind.System, 172 target: ts.ScriptTarget.ES5, 173 jsx: ts.JsxEmit.React, 174 newLine: ts.NewLineKind.LineFeed, 175 moduleResolution: ts.ModuleResolutionKind.NodeJs, 176 allowNonTsExtensions: true // injected by tsserver 177 } as ts.CompilerOptions); 178 }); 179 180 it("Status request gives ts.version", () => { 181 const req: ts.server.protocol.StatusRequest = { 182 command: ts.server.CommandNames.Status, 183 seq: 0, 184 type: "request" 185 }; 186 187 const expected: ts.server.protocol.StatusResponseBody = { 188 version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier 189 }; 190 assert.deepEqual(session.executeCommand(req).response, expected); 191 }); 192 }); 193 194 describe("onMessage", () => { 195 const allCommandNames: ts.server.CommandNames[] = [ 196 ts.server.CommandNames.Brace, 197 ts.server.CommandNames.BraceFull, 198 ts.server.CommandNames.BraceCompletion, 199 ts.server.CommandNames.Change, 200 ts.server.CommandNames.Close, 201 ts.server.CommandNames.Completions, 202 ts.server.CommandNames.CompletionsFull, 203 ts.server.CommandNames.CompletionDetails, 204 ts.server.CommandNames.CompileOnSaveAffectedFileList, 205 ts.server.CommandNames.Configure, 206 ts.server.CommandNames.Definition, 207 ts.server.CommandNames.DefinitionFull, 208 ts.server.CommandNames.DefinitionAndBoundSpan, 209 ts.server.CommandNames.DefinitionAndBoundSpanFull, 210 ts.server.CommandNames.Implementation, 211 ts.server.CommandNames.ImplementationFull, 212 ts.server.CommandNames.Exit, 213 ts.server.CommandNames.FileReferences, 214 ts.server.CommandNames.FileReferencesFull, 215 ts.server.CommandNames.Format, 216 ts.server.CommandNames.Formatonkey, 217 ts.server.CommandNames.FormatFull, 218 ts.server.CommandNames.FormatonkeyFull, 219 ts.server.CommandNames.FormatRangeFull, 220 ts.server.CommandNames.Geterr, 221 ts.server.CommandNames.GeterrForProject, 222 ts.server.CommandNames.SemanticDiagnosticsSync, 223 ts.server.CommandNames.SyntacticDiagnosticsSync, 224 ts.server.CommandNames.SuggestionDiagnosticsSync, 225 ts.server.CommandNames.NavBar, 226 ts.server.CommandNames.NavBarFull, 227 ts.server.CommandNames.Navto, 228 ts.server.CommandNames.NavtoFull, 229 ts.server.CommandNames.NavTree, 230 ts.server.CommandNames.NavTreeFull, 231 ts.server.CommandNames.Occurrences, 232 ts.server.CommandNames.DocumentHighlights, 233 ts.server.CommandNames.DocumentHighlightsFull, 234 ts.server.CommandNames.JsxClosingTag, 235 ts.server.CommandNames.Open, 236 ts.server.CommandNames.Quickinfo, 237 ts.server.CommandNames.QuickinfoFull, 238 ts.server.CommandNames.References, 239 ts.server.CommandNames.ReferencesFull, 240 ts.server.CommandNames.Reload, 241 ts.server.CommandNames.Rename, 242 ts.server.CommandNames.RenameInfoFull, 243 ts.server.CommandNames.RenameLocationsFull, 244 ts.server.CommandNames.Saveto, 245 ts.server.CommandNames.SignatureHelp, 246 ts.server.CommandNames.SignatureHelpFull, 247 ts.server.CommandNames.Status, 248 ts.server.CommandNames.TypeDefinition, 249 ts.server.CommandNames.ProjectInfo, 250 ts.server.CommandNames.ReloadProjects, 251 ts.server.CommandNames.Unknown, 252 ts.server.CommandNames.OpenExternalProject, 253 ts.server.CommandNames.CloseExternalProject, 254 ts.server.CommandNames.SynchronizeProjectList, 255 ts.server.CommandNames.ApplyChangedToOpenFiles, 256 ts.server.CommandNames.EncodedSemanticClassificationsFull, 257 ts.server.CommandNames.Cleanup, 258 ts.server.CommandNames.OutliningSpans, 259 ts.server.CommandNames.TodoComments, 260 ts.server.CommandNames.Indentation, 261 ts.server.CommandNames.DocCommentTemplate, 262 ts.server.CommandNames.CompilerOptionsDiagnosticsFull, 263 ts.server.CommandNames.NameOrDottedNameSpan, 264 ts.server.CommandNames.BreakpointStatement, 265 ts.server.CommandNames.CompilerOptionsForInferredProjects, 266 ts.server.CommandNames.GetCodeFixes, 267 ts.server.CommandNames.GetCodeFixesFull, 268 ts.server.CommandNames.GetSupportedCodeFixes, 269 ts.server.CommandNames.GetApplicableRefactors, 270 ts.server.CommandNames.GetEditsForRefactor, 271 ts.server.CommandNames.GetEditsForRefactorFull, 272 ts.server.CommandNames.OrganizeImports, 273 ts.server.CommandNames.OrganizeImportsFull, 274 ts.server.CommandNames.GetEditsForFileRename, 275 ts.server.CommandNames.GetEditsForFileRenameFull, 276 ts.server.CommandNames.SelectionRange, 277 ts.server.CommandNames.PrepareCallHierarchy, 278 ts.server.CommandNames.ProvideCallHierarchyIncomingCalls, 279 ts.server.CommandNames.ProvideCallHierarchyOutgoingCalls, 280 ts.server.CommandNames.ToggleLineComment, 281 ts.server.CommandNames.ToggleMultilineComment, 282 ts.server.CommandNames.CommentSelection, 283 ts.server.CommandNames.UncommentSelection, 284 ts.server.CommandNames.ProvideInlayHints 285 ]; 286 287 it("should not throw when commands are executed with invalid arguments", () => { 288 let i = 0; 289 for (const name of allCommandNames) { 290 const req: ts.server.protocol.Request = { 291 command: name, 292 seq: i, 293 type: "request" 294 }; 295 i++; 296 session.onMessage(JSON.stringify(req)); 297 req.seq = i; 298 i++; 299 req.arguments = {}; 300 session.onMessage(JSON.stringify(req)); 301 req.seq = i; 302 i++; 303 req.arguments = null; // eslint-disable-line no-null/no-null 304 session.onMessage(JSON.stringify(req)); 305 req.seq = i; 306 i++; 307 req.arguments = ""; 308 session.onMessage(JSON.stringify(req)); 309 req.seq = i; 310 i++; 311 req.arguments = 0; 312 session.onMessage(JSON.stringify(req)); 313 req.seq = i; 314 i++; 315 req.arguments = []; 316 session.onMessage(JSON.stringify(req)); 317 } 318 session.onMessage("GARBAGE NON_JSON DATA"); 319 }); 320 it("should output the response for a correctly handled message", () => { 321 const req: ts.server.protocol.ConfigureRequest = { 322 command: ts.server.CommandNames.Configure, 323 seq: 0, 324 type: "request", 325 arguments: { 326 hostInfo: "unit test", 327 formatOptions: { 328 newLineCharacter: "`n" 329 } 330 } 331 }; 332 333 session.onMessage(JSON.stringify(req)); 334 335 expect(lastSent).to.deep.equal({ 336 command: ts.server.CommandNames.Configure, 337 type: "response", 338 success: true, 339 request_seq: 0, 340 seq: 0, 341 body: undefined, 342 performanceData: undefined, 343 } as ts.server.protocol.ConfigureResponse); 344 }); 345 }); 346 347 describe("send", () => { 348 it("is an overrideable handle which sends protocol messages over the wire", () => { 349 const msg: ts.server.protocol.Request = { seq: 0, type: "request", command: "" }; 350 const strmsg = JSON.stringify(msg); 351 const len = 1 + Utils.byteLength(strmsg, "utf8"); 352 const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`; 353 354 session.send = ts.server.Session.prototype.send; 355 assert(session.send); 356 expect(session.send(msg)).to.not.exist; // eslint-disable-line @typescript-eslint/no-unused-expressions 357 expect(lastWrittenToHost).to.equal(resultMsg); 358 }); 359 }); 360 361 describe("addProtocolHandler", () => { 362 it("can add protocol handlers", () => { 363 const respBody = { 364 item: false 365 }; 366 const command = "newhandle"; 367 const result: ts.server.HandlerResponse = { 368 response: respBody, 369 responseRequired: true 370 }; 371 372 session.addProtocolHandler(command, () => result); 373 374 expect(session.executeCommand({ 375 command, 376 seq: 0, 377 type: "request" 378 })).to.deep.equal(result); 379 }); 380 it("throws when a duplicate handler is passed", () => { 381 const respBody = { 382 item: false 383 }; 384 const resp: ts.server.HandlerResponse = { 385 response: respBody, 386 responseRequired: true 387 }; 388 const command = "newhandle"; 389 390 session.addProtocolHandler(command, () => resp); 391 392 expect(() => session.addProtocolHandler(command, () => resp)) 393 .to.throw(`Protocol handler already exists for command "${command}"`); 394 }); 395 }); 396 397 describe("event", () => { 398 it("can format event responses and send them", () => { 399 const evt = "notify-test"; 400 const info = { 401 test: true 402 }; 403 404 session.event(info, evt); 405 406 expect(lastSent).to.deep.equal({ 407 type: "event", 408 seq: 0, 409 event: evt, 410 body: info 411 }); 412 }); 413 }); 414 415 describe("output", () => { 416 it("can format command responses and send them", () => { 417 const body = { 418 block: { 419 key: "value" 420 } 421 }; 422 const command = "test"; 423 424 session.output(body, command, /*reqSeq*/ 0); 425 426 expect(lastSent).to.deep.equal({ 427 seq: 0, 428 request_seq: 0, 429 type: "response", 430 command, 431 body, 432 success: true, 433 performanceData: undefined, 434 }); 435 }); 436 }); 437}); 438 439describe("unittests:: tsserver:: Session:: exceptions", () => { 440 441 // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test 442 let oldPrepare: ts.AnyFunction; 443 let oldStackTraceLimit: number; 444 before(() => { 445 oldStackTraceLimit = (Error as any).stackTraceLimit; 446 oldPrepare = (Error as any).prepareStackTrace; 447 delete (Error as any).prepareStackTrace; 448 (Error as any).stackTraceLimit = 10; 449 }); 450 451 after(() => { 452 (Error as any).prepareStackTrace = oldPrepare; 453 (Error as any).stackTraceLimit = oldStackTraceLimit; 454 }); 455 456 const command = "testhandler"; 457 class TestSession extends ts.server.Session { 458 lastSent: ts.server.protocol.Message | undefined; 459 private exceptionRaisingHandler(_request: ts.server.protocol.Request): { response?: any, responseRequired: boolean } { 460 f1(); 461 return ts.Debug.fail(); // unreachable, throw to make compiler happy 462 function f1() { 463 throw new Error("myMessage"); 464 } 465 } 466 467 constructor() { 468 super({ 469 host: mockHost, 470 cancellationToken: ts.server.nullCancellationToken, 471 useSingleInferredProject: false, 472 useInferredProjectPerProjectRoot: false, 473 typingsInstaller: undefined!, // TODO: GH#18217 474 byteLength: Utils.byteLength, 475 hrtime: process.hrtime, 476 logger: ts.projectSystem.nullLogger(), 477 canUseEvents: true 478 }); 479 this.addProtocolHandler(command, this.exceptionRaisingHandler); 480 } 481 send(msg: ts.server.protocol.Message) { 482 this.lastSent = msg; 483 } 484 } 485 486 it("raised in a protocol handler generate an event", () => { 487 488 const session = new TestSession(); 489 490 const request = { 491 command, 492 seq: 0, 493 type: "request" 494 }; 495 496 session.onMessage(JSON.stringify(request)); 497 const lastSent = session.lastSent as ts.server.protocol.Response; 498 499 expect(lastSent).to.contain({ 500 seq: 0, 501 type: "response", 502 command, 503 success: false 504 }); 505 506 expect(lastSent.message).has.string("myMessage").and.has.string("f1"); 507 }); 508}); 509 510describe("unittests:: tsserver:: Session:: how Session is extendable via subclassing", () => { 511 class TestSession extends ts.server.Session { 512 lastSent: ts.server.protocol.Message | undefined; 513 customHandler = "testhandler"; 514 constructor() { 515 super({ 516 host: mockHost, 517 cancellationToken: ts.server.nullCancellationToken, 518 useSingleInferredProject: false, 519 useInferredProjectPerProjectRoot: false, 520 typingsInstaller: undefined!, // TODO: GH#18217 521 byteLength: Utils.byteLength, 522 hrtime: process.hrtime, 523 logger: ts.projectSystem.createHasErrorMessageLogger(), 524 canUseEvents: true 525 }); 526 this.addProtocolHandler(this.customHandler, () => { 527 return { response: undefined, responseRequired: true }; 528 }); 529 } 530 send(msg: ts.server.protocol.Message) { 531 this.lastSent = msg; 532 } 533 } 534 535 it("can override methods such as send", () => { 536 const session = new TestSession(); 537 const body = { 538 block: { 539 key: "value" 540 } 541 }; 542 const command = "test"; 543 544 session.output(body, command, /*reqSeq*/ 0); 545 546 expect(session.lastSent).to.deep.equal({ 547 seq: 0, 548 request_seq: 0, 549 type: "response", 550 command, 551 body, 552 success: true, 553 performanceData: undefined, 554 }); 555 }); 556 it("can add and respond to new protocol handlers", () => { 557 const session = new TestSession(); 558 559 expect(session.executeCommand({ 560 seq: 0, 561 type: "request", 562 command: session.customHandler 563 })).to.deep.equal({ 564 response: undefined, 565 responseRequired: true 566 }); 567 }); 568 it("has access to the project service", () => { 569 new class extends TestSession { 570 constructor() { 571 super(); 572 assert(this.projectService); 573 expect(this.projectService).to.be.instanceOf(ts.server.ProjectService); 574 } 575 }(); 576 }); 577}); 578 579describe("unittests:: tsserver:: Session:: an example of using the Session API to create an in-process server", () => { 580 class InProcSession extends ts.server.Session { 581 private queue: ts.server.protocol.Request[] = []; 582 constructor(private client: InProcClient) { 583 super({ 584 host: mockHost, 585 cancellationToken: ts.server.nullCancellationToken, 586 useSingleInferredProject: false, 587 useInferredProjectPerProjectRoot: false, 588 typingsInstaller: undefined!, // TODO: GH#18217 589 byteLength: Utils.byteLength, 590 hrtime: process.hrtime, 591 logger: ts.projectSystem.createHasErrorMessageLogger(), 592 canUseEvents: true 593 }); 594 this.addProtocolHandler("echo", (req: ts.server.protocol.Request) => ({ 595 response: req.arguments, 596 responseRequired: true 597 })); 598 } 599 600 send(msg: ts.server.protocol.Message) { 601 this.client.handle(msg); 602 } 603 604 enqueue(msg: ts.server.protocol.Request) { 605 this.queue.unshift(msg); 606 } 607 608 handleRequest(msg: ts.server.protocol.Request) { 609 let response: ts.server.protocol.Response; 610 try { 611 response = this.executeCommand(msg).response as ts.server.protocol.Response; 612 } 613 catch (e) { 614 this.output(undefined, msg.command, msg.seq, e.toString()); 615 return; 616 } 617 if (response) { 618 this.output(response, msg.command, msg.seq); 619 } 620 } 621 622 consumeQueue() { 623 while (this.queue.length > 0) { 624 const elem = this.queue.pop()!; 625 this.handleRequest(elem); 626 } 627 } 628 } 629 630 class InProcClient { 631 private server: InProcSession | undefined; 632 private seq = 0; 633 private callbacks: ((resp: ts.server.protocol.Response) => void)[] = []; 634 private eventHandlers = new ts.Map<string, (args: any) => void>(); 635 636 handle(msg: ts.server.protocol.Message): void { 637 if (msg.type === "response") { 638 const response = msg as ts.server.protocol.Response; 639 const handler = this.callbacks[response.request_seq]; 640 if (handler) { 641 handler(response); 642 delete this.callbacks[response.request_seq]; 643 } 644 } 645 else if (msg.type === "event") { 646 const event = msg as ts.server.protocol.Event; 647 this.emit(event.event, event.body); 648 } 649 } 650 651 emit(name: string, args: any): void { 652 const handler = this.eventHandlers.get(name); 653 if (handler) { 654 handler(args); 655 } 656 } 657 658 on(name: string, handler: (args: any) => void): void { 659 this.eventHandlers.set(name, handler); 660 } 661 662 connect(session: InProcSession): void { 663 this.server = session; 664 } 665 666 execute(command: string, args: any, callback: (resp: ts.server.protocol.Response) => void): void { 667 if (!this.server) { 668 return; 669 } 670 this.seq++; 671 this.server.enqueue({ 672 seq: this.seq, 673 type: "request", 674 command, 675 arguments: args 676 }); 677 this.callbacks[this.seq] = callback; 678 } 679 } 680 681 it("can be constructed and respond to commands", (done) => { 682 const cli = new InProcClient(); 683 const session = new InProcSession(cli); 684 const toEcho = { 685 data: true 686 }; 687 const toEvent = { 688 data: false 689 }; 690 let responses = 0; 691 692 // Connect the client 693 cli.connect(session); 694 695 // Add an event handler 696 cli.on("testevent", (eventinfo) => { 697 expect(eventinfo).to.equal(toEvent); 698 responses++; 699 expect(responses).to.equal(1); 700 }); 701 702 // Trigger said event from the server 703 session.event(toEvent, "testevent"); 704 705 // Queue an echo command 706 cli.execute("echo", toEcho, (resp) => { 707 assert(resp.success, resp.message); 708 responses++; 709 expect(responses).to.equal(2); 710 expect(resp.body).to.deep.equal(toEcho); 711 }); 712 713 // Queue a configure command 714 cli.execute("configure", { 715 hostInfo: "unit test", 716 formatOptions: { 717 newLineCharacter: "`n" 718 } 719 }, (resp) => { 720 assert(resp.success, resp.message); 721 responses++; 722 expect(responses).to.equal(3); 723 done(); 724 }); 725 726 // Consume the queue and trigger the callbacks 727 session.consumeQueue(); 728 }); 729}); 730 731describe("unittests:: tsserver:: Session:: helpers", () => { 732 it(ts.server.getLocationInNewDocument.name, () => { 733 const text = `// blank line\nconst x = 0;`; 734 const renameLocationInOldText = text.indexOf("0"); 735 const fileName = "/a.ts"; 736 const edits: ts.FileTextChanges = { 737 fileName, 738 textChanges: [ 739 { 740 span: { start: 0, length: 0 }, 741 newText: "const newLocal = 0;\n\n", 742 }, 743 { 744 span: { start: renameLocationInOldText, length: 1 }, 745 newText: "newLocal", 746 }, 747 ], 748 }; 749 const renameLocationInNewText = renameLocationInOldText + edits.textChanges[0].newText.length; 750 const res = ts.server.getLocationInNewDocument(text, fileName, renameLocationInNewText, [edits]); 751 assert.deepEqual(res, { line: 4, offset: 11 }); 752 }); 753}); 754