• 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        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