• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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