• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: with project references and tsbuild", () => {
3        function createHost(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) {
4            const host = createServerHost(files);
5            // ts build should succeed
6            tscWatch.ensureErrorFreeBuild(host, rootNames);
7            return host;
8        }
9
10        describe("with container project", () => {
11            function getProjectFiles(project: string): [File, File] {
12                return [
13                    TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"),
14                    TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"),
15                ];
16            }
17
18            const project = "container";
19            const containerLib = getProjectFiles("container/lib");
20            const containerExec = getProjectFiles("container/exec");
21            const containerCompositeExec = getProjectFiles("container/compositeExec");
22            const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json");
23            const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig];
24
25            it("does not error on container only project", () => {
26                const host = createHost(files, [containerConfig.path]);
27
28                // Open external project for the folder
29                const session = createSession(host);
30                const service = session.getProjectService();
31                service.openExternalProjects([{
32                    projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project),
33                    rootFiles: files.map(f => ({ fileName: f.path })),
34                    options: {}
35                }]);
36                checkNumberOfProjects(service, { configuredProjects: 4 });
37                files.forEach(f => {
38                    const args: protocol.FileRequestArgs = {
39                        file: f.path,
40                        projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined
41                    };
42                    const syntaxDiagnostics = session.executeCommandSeq<protocol.SyntacticDiagnosticsSyncRequest>({
43                        command: protocol.CommandTypes.SyntacticDiagnosticsSync,
44                        arguments: args
45                    }).response;
46                    assert.deepEqual(syntaxDiagnostics, []);
47                    const semanticDiagnostics = session.executeCommandSeq<protocol.SemanticDiagnosticsSyncRequest>({
48                        command: protocol.CommandTypes.SemanticDiagnosticsSync,
49                        arguments: args
50                    }).response;
51                    assert.deepEqual(semanticDiagnostics, []);
52                });
53                const containerProject = service.configuredProjects.get(containerConfig.path)!;
54                checkProjectActualFiles(containerProject, [containerConfig.path]);
55                const optionsDiagnostics = session.executeCommandSeq<protocol.CompilerOptionsDiagnosticsRequest>({
56                    command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull,
57                    arguments: { projectFileName: containerProject.projectName }
58                }).response;
59                assert.deepEqual(optionsDiagnostics, []);
60            });
61
62            it("can successfully find references with --out options", () => {
63                const host = createHost(files, [containerConfig.path]);
64                const session = createSession(host);
65                openFilesForSession([containerCompositeExec[1]], session);
66                const service = session.getProjectService();
67                checkNumberOfProjects(service, { configuredProjects: 2 }); // compositeExec and solution
68                const solutionProject = service.configuredProjects.get(containerConfig.path)!;
69                assert.isTrue(solutionProject.isInitialLoadPending());
70                const { file: myConstFile, start: myConstStart, end: myConstEnd } = protocolFileSpanFromSubstring({
71                    file: containerCompositeExec[1],
72                    text: "myConst",
73                });
74                const response = session.executeCommandSeq<protocol.RenameRequest>({
75                    command: protocol.CommandTypes.Rename,
76                    arguments: { file: myConstFile, ...myConstStart }
77                }).response as protocol.RenameResponseBody;
78
79                const locationOfMyConstInLib = protocolFileSpanWithContextFromSubstring({
80                    file: containerLib[1],
81                    text: "myConst",
82                    contextText: "export const myConst = 30;"
83                });
84                const { file: _, ...renameTextOfMyConstInLib } = locationOfMyConstInLib;
85                const locationOfMyConstInExec = protocolFileSpanWithContextFromSubstring({
86                    file: containerExec[1],
87                    text: "myConst"
88                });
89                const { file: myConstInExecFile, ...renameTextOfMyConstInExec } = locationOfMyConstInExec;
90                assert.deepEqual(response.locs, [
91                    { file: locationOfMyConstInLib.file, locs: [renameTextOfMyConstInLib] },
92                    { file: myConstFile, locs: [{ start: myConstStart, end: myConstEnd }] },
93                    { file: myConstInExecFile, locs: [renameTextOfMyConstInExec] },
94                ]);
95                checkNumberOfProjects(service, { configuredProjects: 4 });
96                assert.isFalse(solutionProject.isInitialLoadPending());
97            });
98
99            it("ancestor and project ref management", () => {
100                const tempFile: File = {
101                    path: `/user/username/projects/temp/temp.ts`,
102                    content: "let x = 10"
103                };
104                const host = createHost(files.concat([tempFile]), [containerConfig.path]);
105                const session = createSession(host);
106                openFilesForSession([containerCompositeExec[1]], session);
107                const service = session.getProjectService();
108                checkNumberOfProjects(service, { configuredProjects: 2 }); // compositeExec and solution
109                const solutionProject = service.configuredProjects.get(containerConfig.path)!;
110                assert.isTrue(solutionProject.isInitialLoadPending());
111
112                // Open temp file and verify all projects alive
113                openFilesForSession([tempFile], session);
114                checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 });
115                assert.isTrue(solutionProject.isInitialLoadPending());
116
117                const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst");
118                session.executeCommandSeq<protocol.RenameRequest>({
119                    command: protocol.CommandTypes.Rename,
120                    arguments: {
121                        file: containerCompositeExec[1].path,
122                        ...locationOfMyConst
123                    }
124                });
125
126                // Ref projects are loaded
127                checkNumberOfProjects(service, { configuredProjects: 4, inferredProjects: 1 });
128                assert.isFalse(solutionProject.isInitialLoadPending());
129
130                // Open temp file and verify all projects alive
131                service.closeClientFile(tempFile.path);
132                openFilesForSession([tempFile], session);
133                checkNumberOfProjects(service, { configuredProjects: 4, inferredProjects: 1 });
134
135                // Close all files and open temp file, only inferred project should be alive
136                service.closeClientFile(containerCompositeExec[1].path);
137                service.closeClientFile(tempFile.path);
138                openFilesForSession([tempFile], session);
139                checkNumberOfProjects(service, { inferredProjects: 1 });
140            });
141        });
142
143        describe("with main and depedency project", () => {
144            const dependecyLocation = `${tscWatch.projectRoot}/dependency`;
145            const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`;
146            const mainLocation = `${tscWatch.projectRoot}/main`;
147            const dependencyTs: File = {
148                path: `${dependecyLocation}/FnS.ts`,
149                content: `export function fn1() { }
150export function fn2() { }
151export function fn3() { }
152export function fn4() { }
153export function fn5() { }
154`
155            };
156            const dependencyTsPath = dependencyTs.path.toLowerCase();
157            const dependencyConfig: File = {
158                path: `${dependecyLocation}/tsconfig.json`,
159                content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } })
160            };
161
162            const mainTs: File = {
163                path: `${mainLocation}/main.ts`,
164                content: `import {
165    fn1,
166    fn2,
167    fn3,
168    fn4,
169    fn5
170} from '../decls/fns'
171
172fn1();
173fn2();
174fn3();
175fn4();
176fn5();
177`
178            };
179            const mainConfig: File = {
180                path: `${mainLocation}/tsconfig.json`,
181                content: JSON.stringify({
182                    compilerOptions: { composite: true, declarationMap: true },
183                    references: [{ path: "../dependency" }]
184                })
185            };
186
187            const randomFile: File = {
188                path: `${tscWatch.projectRoot}/random/random.ts`,
189                content: "let a = 10;"
190            };
191            const randomConfig: File = {
192                path: `${tscWatch.projectRoot}/random/tsconfig.json`,
193                content: "{}"
194            };
195            const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`;
196            const dtsPath = dtsLocation.toLowerCase() as Path;
197            const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`;
198            const dtsMapPath = dtsMapLocation.toLowerCase() as Path;
199
200            const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig];
201
202            function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo: string) {
203                checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo);
204                checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo);
205            }
206
207            function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], reqName: string) {
208                verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path), reqName);
209            }
210
211            function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) {
212                verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random");
213            }
214
215            function declarationSpan(fn: number): protocol.TextSpanWithContext {
216                return {
217                    start: { line: fn, offset: 17 },
218                    end: { line: fn, offset: 20 },
219                    contextStart: { line: fn, offset: 1 },
220                    contextEnd: { line: fn, offset: 26 }
221                };
222            }
223            function importSpan(fn: number): protocol.TextSpanWithContext {
224                return {
225                    start: { line: fn + 1, offset: 5 },
226                    end: { line: fn + 1, offset: 8 },
227                    contextStart: { line: 1, offset: 1 },
228                    contextEnd: { line: 7, offset: 22 }
229                };
230            }
231            function usageSpan(fn: number): protocol.TextSpan {
232                return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } };
233            }
234
235            function goToDefFromMainTs(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> {
236                const textSpan = usageSpan(fn);
237                const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) };
238                return {
239                    reqName: "goToDef",
240                    request: {
241                        command: protocol.CommandTypes.DefinitionAndBoundSpan,
242                        arguments: { file: mainTs.path, ...textSpan.start }
243                    },
244                    expectedResponse: {
245                        // To dependency
246                        definitions: [definition],
247                        textSpan
248                    }
249                };
250            }
251
252            function goToDefFromMainTsWithNoMap(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> {
253                const textSpan = usageSpan(fn);
254                const definition = declarationSpan(fn);
255                const declareSpaceLength = "declare ".length;
256                return {
257                    reqName: "goToDef",
258                    request: {
259                        command: protocol.CommandTypes.DefinitionAndBoundSpan,
260                        arguments: { file: mainTs.path, ...textSpan.start }
261                    },
262                    expectedResponse: {
263                        // To the dts
264                        definitions: [{
265                            file: dtsPath,
266                            start: { line: fn, offset: definition.start.offset + declareSpaceLength },
267                            end: { line: fn, offset: definition.end.offset + declareSpaceLength },
268                            contextStart: { line: fn, offset: 1 },
269                            contextEnd: { line: fn, offset: 37 }
270                        }],
271                        textSpan
272                    }
273                };
274            }
275
276            function goToDefFromMainTsWithNoDts(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> {
277                const textSpan = usageSpan(fn);
278                return {
279                    reqName: "goToDef",
280                    request: {
281                        command: protocol.CommandTypes.DefinitionAndBoundSpan,
282                        arguments: { file: mainTs.path, ...textSpan.start }
283                    },
284                    expectedResponse: {
285                        // To import declaration
286                        definitions: [{ file: mainTs.path, ...importSpan(fn) }],
287                        textSpan
288                    }
289                };
290            }
291
292            function goToDefFromMainTsWithDependencyChange(fn: number): Action<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> {
293                const textSpan = usageSpan(fn);
294                return {
295                    reqName: "goToDef",
296                    request: {
297                        command: protocol.CommandTypes.DefinitionAndBoundSpan,
298                        arguments: { file: mainTs.path, ...textSpan.start }
299                    },
300                    expectedResponse: {
301                        // Definition on fn + 1 line
302                        definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }],
303                        textSpan
304                    }
305                };
306            }
307
308            function goToDefFromMainTsProjectInfoVerifier(withRefs: boolean): ProjectInfoVerifier {
309                return {
310                    openFile: mainTs,
311                    openFileLastLine: 14,
312                    configFile: mainConfig,
313                    expectedProjectActualFiles: withRefs ?
314                        [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] :
315                        [mainTs.path, libFile.path, mainConfig.path, dtsPath]
316                };
317            }
318
319            function renameFromDependencyTs(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> {
320                const defSpan = declarationSpan(fn);
321                const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan;
322                return {
323                    reqName: "rename",
324                    request: {
325                        command: protocol.CommandTypes.Rename,
326                        arguments: { file: dependencyTs.path, ...triggerSpan.start }
327                    },
328                    expectedResponse: {
329                        info: {
330                            canRename: true,
331                            fileToRename: undefined,
332                            displayName: `fn${fn}`,
333                            fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`,
334                            kind: ScriptElementKind.functionElement,
335                            kindModifiers: "export",
336                            triggerSpan
337                        },
338                        locs: [
339                            { file: dependencyTs.path, locs: [defSpan] }
340                        ]
341                    }
342                };
343            }
344
345            function renameFromDependencyTsWithDependencyChange(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> {
346                const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1);
347
348                return {
349                    ...rest,
350                    expectedResponse: {
351                        info: {
352                            ...info as protocol.RenameInfoSuccess,
353                            displayName: `fn${fn}`,
354                            fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`,
355                        },
356                        locs
357                    }
358                };
359            }
360
361            function renameFromDependencyTsProjectInfoVerifier(): ProjectInfoVerifier {
362                return {
363                    openFile: dependencyTs,
364                    openFileLastLine: 6,
365                    configFile: dependencyConfig,
366                    expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path]
367                };
368            }
369
370            function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> {
371                const { reqName, request, expectedResponse } = renameFromDependencyTs(fn);
372                const { info, locs } = expectedResponse;
373                return {
374                    reqName,
375                    request,
376                    expectedResponse: {
377                        info,
378                        locs: [
379                            locs[0],
380                            {
381                                file: mainTs.path,
382                                locs: [
383                                    importSpan(fn),
384                                    usageSpan(fn)
385                                ]
386                            }
387                        ]
388                    }
389                };
390            }
391
392            function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action<protocol.RenameRequest, protocol.RenameResponseBody> {
393                const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn);
394                const { info, locs } = expectedResponse;
395                return {
396                    reqName,
397                    request,
398                    expectedResponse: {
399                        info,
400                        locs: [
401                            locs[0],
402                            {
403                                file: mainTs.path,
404                                locs: [
405                                    importSpan(fn),
406                                    usageSpan(fn)
407                                ]
408                            }
409                        ]
410                    }
411                };
412            }
413
414            function removePath(array: readonly string[], ...delPaths: string[]) {
415                return array.filter(a => {
416                    const aLower = a.toLowerCase();
417                    return delPaths.every(dPath => dPath !== aLower);
418                });
419            }
420
421            interface Action<Req = protocol.Request, Response = {}> {
422                reqName: string;
423                request: Partial<Req>;
424                expectedResponse: Response;
425            }
426            interface ActionInfo<Req = protocol.Request, Response = {}> {
427                action: (fn: number) => Action<Req, Response>;
428                closedInfos: readonly string[];
429                otherWatchedFiles: readonly string[];
430                expectsDts: boolean;
431                expectsMap: boolean;
432                freshMapInfo?: boolean;
433                freshDocumentMapper?: boolean;
434                skipDtsMapCheck?: boolean;
435            }
436            type ActionKey = keyof ActionInfoVerifier;
437            type ActionInfoGetterFn<Req = protocol.Request, Response = {}> = () => ActionInfo<Req, Response>;
438            type ActionInfoSpreader<Req = protocol.Request, Response = {}> = [
439                ActionKey, // Key to get initial value and pass this value to spread function
440                (actionInfo: ActionInfo<Req, Response>) => Partial<ActionInfo<Req, Response>>
441            ];
442            type ActionInfoGetter<Req = protocol.Request, Response = {}> = ActionInfoGetterFn<Req, Response> | ActionKey | ActionInfoSpreader<Req, Response>;
443            interface ProjectInfoVerifier {
444                openFile: File;
445                openFileLastLine: number;
446                configFile: File;
447                expectedProjectActualFiles: readonly string[];
448            }
449            interface ActionInfoVerifier<Req = protocol.Request, Response = {}> {
450                main: ActionInfoGetter<Req, Response>;
451                change: ActionInfoGetter<Req, Response>;
452                dtsChange: ActionInfoGetter<Req, Response>;
453                mapChange: ActionInfoGetter<Req, Response>;
454                noMap: ActionInfoGetter<Req, Response>;
455                mapFileCreated: ActionInfoGetter<Req, Response>;
456                mapFileDeleted: ActionInfoGetter<Req, Response>;
457                noDts: ActionInfoGetter<Req, Response>;
458                dtsFileCreated: ActionInfoGetter<Req, Response>;
459                dtsFileDeleted: ActionInfoGetter<Req, Response>;
460                dependencyChange: ActionInfoGetter<Req, Response>;
461                noBuild: ActionInfoGetter<Req, Response>;
462            }
463            interface DocumentPositionMapperVerifier<Req = protocol.Request, Response = {}> extends ProjectInfoVerifier, ActionInfoVerifier<Req, Response> {
464            }
465
466            interface VerifierAndWithRefs {
467                withRefs: boolean;
468                disableSourceOfProjectReferenceRedirect?: true;
469                verifier: (withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) => readonly DocumentPositionMapperVerifier[];
470            }
471
472            function openFiles(verifiers: readonly DocumentPositionMapperVerifier[]) {
473                return verifiers.map(v => v.openFile);
474            }
475            interface OpenTsFile extends VerifierAndWithRefs {
476                onHostCreate?: (host: TestServerHost) => void;
477            }
478            function openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier, onHostCreate }: OpenTsFile) {
479                const host = createHost(files, [mainConfig.path]);
480                if (!withRefs) {
481                    // Erase project reference
482                    host.writeFile(mainConfig.path, JSON.stringify({
483                        compilerOptions: { composite: true, declarationMap: true }
484                    }));
485                }
486                else if (disableSourceOfProjectReferenceRedirect) {
487                    // Erase project reference
488                    host.writeFile(mainConfig.path, JSON.stringify({
489                        compilerOptions: {
490                            composite: true,
491                            declarationMap: true,
492                            disableSourceOfProjectReferenceRedirect: !!disableSourceOfProjectReferenceRedirect
493                        },
494                        references: [{ path: "../dependency" }]
495                    }));
496                }
497                if (onHostCreate) {
498                    onHostCreate(host);
499                }
500                const session = createSession(host);
501                const verifiers = verifier(withRefs && !disableSourceOfProjectReferenceRedirect, disableSourceOfProjectReferenceRedirect);
502                openFilesForSession([...openFiles(verifiers), randomFile], session);
503                return { host, session, verifiers };
504            }
505
506            function checkProject(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[], noDts?: true) {
507                const service = session.getProjectService();
508                checkNumberOfProjects(service, { configuredProjects: 1 + verifiers.length });
509                verifiers.forEach(({ configFile, expectedProjectActualFiles }) => {
510                    checkProjectActualFiles(
511                        service.configuredProjects.get(configFile.path.toLowerCase())!,
512                        noDts ?
513                            expectedProjectActualFiles.filter(f => f.toLowerCase() !== dtsPath) :
514                            expectedProjectActualFiles
515                    );
516                });
517            }
518
519            function firstAction(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) {
520                for (const { action } of getActionInfo(verifiers, "main")) {
521                    const { request } = action(1);
522                    session.executeCommandSeq(request);
523                }
524            }
525
526            function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) {
527                const { response } = session.executeCommandSeq(request);
528                assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`);
529            }
530
531            function verifyScriptInfoPresence(session: TestSession, path: string, expectedToBePresent: boolean, reqName: string) {
532                const info = session.getProjectService().filenameToScriptInfo.get(path);
533                if (expectedToBePresent) {
534                    assert.isDefined(info, `${reqName}:: ${path} expected to be present`);
535                }
536                else {
537                    assert.isUndefined(info, `${reqName}:: ${path} expected to be not present`);
538                }
539                return info;
540            }
541
542            interface VerifyDocumentPositionMapper {
543                session: TestSession;
544                dependencyMap: server.ScriptInfo | undefined;
545                documentPositionMapper: server.ScriptInfo["documentPositionMapper"];
546                equal: boolean;
547                debugInfo: string;
548            }
549            function verifyDocumentPositionMapper({ session, dependencyMap, documentPositionMapper, equal, debugInfo }: VerifyDocumentPositionMapper) {
550                assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, debugInfo);
551                if (dependencyMap) {
552                    if (equal) {
553                        assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo);
554                    }
555                    else {
556                        assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo);
557                    }
558                }
559            }
560
561            function getActionInfoOfVerfier(verifier: DocumentPositionMapperVerifier, actionKey: ActionKey): ActionInfo {
562                const actionInfoGetter = verifier[actionKey];
563                if (isString(actionInfoGetter)) {
564                    return getActionInfoOfVerfier(verifier, actionInfoGetter);
565                }
566
567                if (isArray(actionInfoGetter)) {
568                    const initialValue = getActionInfoOfVerfier(verifier, actionInfoGetter[0]);
569                    return {
570                        ...initialValue,
571                        ...actionInfoGetter[1](initialValue)
572                    };
573                }
574
575                return actionInfoGetter();
576            }
577
578            function getActionInfo(verifiers: readonly DocumentPositionMapperVerifier[], actionKey: ActionKey): ActionInfo[] {
579                return verifiers.map(v => getActionInfoOfVerfier(v, actionKey));
580            }
581
582            interface VerifyAllFnAction {
583                session: TestSession;
584                host: TestServerHost;
585                verifiers: readonly DocumentPositionMapperVerifier[];
586                actionKey: ActionKey;
587                sourceMapPath?: server.ScriptInfo["sourceMapFilePath"];
588                dependencyMap?: server.ScriptInfo | undefined;
589                documentPositionMapper?: server.ScriptInfo["documentPositionMapper"];
590            }
591            interface VerifyAllFnActionResult {
592                actionInfos: readonly ActionInfo[];
593                actionKey: ActionKey;
594                dependencyMap: server.ScriptInfo | undefined;
595                documentPositionMapper: server.ScriptInfo["documentPositionMapper"] | undefined;
596            }
597            function verifyAllFnAction({
598                session,
599                host,
600                verifiers,
601                actionKey,
602                dependencyMap,
603                documentPositionMapper,
604            }: VerifyAllFnAction): VerifyAllFnActionResult {
605                const actionInfos = getActionInfo(verifiers, actionKey);
606                let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined;
607                // action
608                let first = true;
609                for (const {
610                    action,
611                    closedInfos,
612                    otherWatchedFiles,
613                    expectsDts,
614                    expectsMap,
615                    freshMapInfo,
616                    freshDocumentMapper,
617                    skipDtsMapCheck
618                } of actionInfos) {
619                    for (let fn = 1; fn <= 5; fn++) {
620                        const fnAction = action(fn);
621                        verifyAction(session, fnAction);
622                        const debugInfo = `${actionKey}:: ${fnAction.reqName}:: ${fn}`;
623                        const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, debugInfo);
624                        const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, debugInfo);
625                        verifyInfosWithRandom(
626                            session,
627                            host,
628                            openFiles(verifiers).map(f => f.path),
629                            closedInfos,
630                            otherWatchedFiles,
631                            debugInfo
632                        );
633
634                        if (dtsInfo) {
635                            if (first || (fn === 1 && freshMapInfo)) {
636                                if (!skipDtsMapCheck) {
637                                    if (dtsMapInfo) {
638                                        assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, debugInfo);
639                                    }
640                                    else {
641                                        assert.isNotString(dtsInfo.sourceMapFilePath, debugInfo);
642                                        assert.isNotFalse(dtsInfo.sourceMapFilePath, debugInfo);
643                                        assert.isDefined(dtsInfo.sourceMapFilePath, debugInfo);
644                                    }
645                                }
646                            }
647                            else {
648                                assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, debugInfo);
649                            }
650                        }
651
652                        if (!first && (fn !== 1 || !freshMapInfo)) {
653                            verifyDocumentPositionMapper({
654                                session,
655                                dependencyMap,
656                                documentPositionMapper,
657                                equal: fn !== 1 || !freshDocumentMapper,
658                                debugInfo
659                            });
660                        }
661                        sourceMapPath = dtsInfo && dtsInfo.sourceMapFilePath;
662                        dependencyMap = dtsMapInfo;
663                        documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper;
664                        first = false;
665                    }
666                }
667
668                return { actionInfos, actionKey, dependencyMap, documentPositionMapper };
669            }
670
671            function verifyScriptInfoCollection(
672                session: TestSession,
673                host: TestServerHost,
674                verifiers: readonly DocumentPositionMapperVerifier[],
675                { dependencyMap, documentPositionMapper, actionInfos, actionKey }: VerifyAllFnActionResult
676            ) {
677                // Collecting at this point retains dependency.d.ts and map
678                closeFilesForSession([randomFile], session);
679                openFilesForSession([randomFile], session);
680
681                const { closedInfos, otherWatchedFiles } = last(actionInfos);
682                const debugInfo = `${actionKey} Collection`;
683                verifyInfosWithRandom(
684                    session,
685                    host,
686                    openFiles(verifiers).map(f => f.path),
687                    closedInfos,
688                    otherWatchedFiles,
689                    debugInfo
690                );
691                verifyDocumentPositionMapper({
692                    session,
693                    dependencyMap,
694                    documentPositionMapper,
695                    equal: true,
696                    debugInfo
697                });
698
699                // Closing open file, removes dependencies too
700                closeFilesForSession([...openFiles(verifiers), randomFile], session);
701                openFilesForSession([randomFile], session);
702                verifyOnlyRandomInfos(session, host);
703            }
704
705            function verifyScenarioAndScriptInfoCollection(
706                session: TestSession,
707                host: TestServerHost,
708                verifiers: readonly DocumentPositionMapperVerifier[],
709                actionKey: ActionKey,
710                noDts?: true
711            ) {
712                // Main scenario action
713                const result = verifyAllFnAction({ session, host, verifiers, actionKey });
714                checkProject(session, verifiers, noDts);
715                verifyScriptInfoCollection(session, host, verifiers, result);
716            }
717
718            function verifyScenarioWithChangesWorker(
719                {
720                    scenarioName,
721                    verifier,
722                    withRefs,
723                    disableSourceOfProjectReferenceRedirect,
724                    change,
725                    afterChangeActionKey
726                }: VerifyScenarioWithChanges,
727                timeoutBeforeAction: boolean,
728            ) {
729                it(scenarioName, () => {
730                    const { host, session, verifiers } = openTsFile({ verifier, withRefs, disableSourceOfProjectReferenceRedirect });
731
732                    // Create DocumentPositionMapper
733                    firstAction(session, verifiers);
734                    const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath);
735                    const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper;
736
737                    // change
738                    change(host, session, verifiers);
739                    if (timeoutBeforeAction) {
740                        host.runQueuedTimeoutCallbacks();
741                        checkProject(session, verifiers);
742                        verifyDocumentPositionMapper({
743                            session,
744                            dependencyMap,
745                            documentPositionMapper,
746                            equal: true,
747                            debugInfo: "After change timeout"
748                        });
749                    }
750
751                    // action
752                    verifyAllFnAction({
753                        session,
754                        host,
755                        verifiers,
756                        actionKey: afterChangeActionKey,
757                        dependencyMap,
758                        documentPositionMapper
759                    });
760                });
761            }
762
763            interface VerifyScenarioWithChanges extends VerifierAndWithRefs {
764                scenarioName: string;
765                change: (host: TestServerHost, session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) => void;
766                afterChangeActionKey: ActionKey;
767            }
768            function verifyScenarioWithChanges(verify: VerifyScenarioWithChanges) {
769                describe("when timeout occurs before request", () => {
770                    verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ true);
771                });
772
773                describe("when timeout does not occur before request", () => {
774                    verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ false);
775                });
776            }
777
778            interface VerifyScenarioWhenFileNotPresent extends VerifierAndWithRefs {
779                scenarioName: string;
780                fileLocation: string;
781                fileNotPresentKey: ActionKey;
782                fileCreatedKey: ActionKey;
783                fileDeletedKey: ActionKey;
784                noDts?: true;
785            }
786            function verifyScenarioWhenFileNotPresent({
787                scenarioName,
788                verifier,
789                withRefs,
790                disableSourceOfProjectReferenceRedirect,
791                fileLocation,
792                fileNotPresentKey,
793                fileCreatedKey,
794                fileDeletedKey,
795                noDts
796            }: VerifyScenarioWhenFileNotPresent) {
797                describe(scenarioName, () => {
798                    it("when file is not present", () => {
799                        const { host, session, verifiers } = openTsFile({
800                            verifier,
801                            withRefs,
802                            disableSourceOfProjectReferenceRedirect,
803                            onHostCreate: host => host.deleteFile(fileLocation)
804                        });
805                        checkProject(session, verifiers, noDts);
806
807                        verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileNotPresentKey, noDts);
808                    });
809
810                    it("when file is created after actions on projects", () => {
811                        let fileContents: string | undefined;
812                        const { host, session, verifiers } = openTsFile({
813                            verifier,
814                            withRefs,
815                            disableSourceOfProjectReferenceRedirect,
816                            onHostCreate: host => {
817                                fileContents = host.readFile(fileLocation);
818                                host.deleteFile(fileLocation);
819                            }
820                        });
821                        firstAction(session, verifiers);
822
823                        host.writeFile(fileLocation, fileContents!);
824                        verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileCreatedKey);
825                    });
826
827                    it("when file is deleted after actions on the projects", () => {
828                        const { host, session, verifiers } = openTsFile({ verifier, disableSourceOfProjectReferenceRedirect, withRefs });
829                        firstAction(session, verifiers);
830
831                        // The dependency file is deleted when orphan files are collected
832                        host.deleteFile(fileLocation);
833                        // Verify with deleted action key
834                        verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey });
835                        checkProject(session, verifiers, noDts);
836
837                        // Script info collection should behave as fileNotPresentKey
838                        verifyScriptInfoCollection(
839                            session,
840                            host,
841                            verifiers,
842                            {
843                                actionInfos: getActionInfo(verifiers, fileNotPresentKey),
844                                actionKey: fileNotPresentKey,
845                                dependencyMap: undefined,
846                                documentPositionMapper: undefined
847                            }
848                        );
849                    });
850                });
851            }
852
853            function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) {
854                it(mainScenario, () => {
855                    const { host, session, verifiers } = openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier });
856                    checkProject(session, verifiers);
857                    verifyScenarioAndScriptInfoCollection(session, host, verifiers, "main");
858                });
859
860                // Edit
861                verifyScenarioWithChanges({
862                    scenarioName: "when usage file changes, document position mapper doesnt change",
863                    verifier,
864                    withRefs,
865                    disableSourceOfProjectReferenceRedirect,
866                    change: (_host, session, verifiers) => verifiers.forEach(
867                        verifier => session.executeCommandSeq<protocol.ChangeRequest>({
868                            command: protocol.CommandTypes.Change,
869                            arguments: {
870                                file: verifier.openFile.path,
871                                line: verifier.openFileLastLine,
872                                offset: 1,
873                                endLine: verifier.openFileLastLine,
874                                endOffset: 1,
875                                insertString: "const x = 10;"
876                            }
877                        })
878                    ),
879                    afterChangeActionKey: "change"
880                });
881
882                // Edit dts to add new fn
883                verifyScenarioWithChanges({
884                    scenarioName: "when dependency .d.ts changes, document position mapper doesnt change",
885                    verifier,
886                    withRefs,
887                    disableSourceOfProjectReferenceRedirect,
888                    change: host => host.writeFile(
889                        dtsLocation,
890                        host.readFile(dtsLocation)!.replace(
891                            "//# sourceMappingURL=FnS.d.ts.map",
892                            `export declare function fn6(): void;
893//# sourceMappingURL=FnS.d.ts.map`
894                        )
895                    ),
896                    afterChangeActionKey: "dtsChange"
897                });
898
899                // Edit map file to represent added new line
900                verifyScenarioWithChanges({
901                    scenarioName: "when dependency file's map changes",
902                    verifier,
903                    withRefs,
904                    disableSourceOfProjectReferenceRedirect,
905                    change: host => host.writeFile(
906                        dtsMapLocation,
907                        `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}`
908                    ),
909                    afterChangeActionKey: "mapChange"
910                });
911
912                verifyScenarioWhenFileNotPresent({
913                    scenarioName: "with depedency files map file",
914                    verifier,
915                    withRefs,
916                    disableSourceOfProjectReferenceRedirect,
917                    fileLocation: dtsMapLocation,
918                    fileNotPresentKey: "noMap",
919                    fileCreatedKey: "mapFileCreated",
920                    fileDeletedKey: "mapFileDeleted"
921                });
922
923                verifyScenarioWhenFileNotPresent({
924                    scenarioName: "with depedency .d.ts file",
925                    verifier,
926                    withRefs,
927                    disableSourceOfProjectReferenceRedirect,
928                    fileLocation: dtsLocation,
929                    fileNotPresentKey: "noDts",
930                    fileCreatedKey: "dtsFileCreated",
931                    fileDeletedKey: "dtsFileDeleted",
932                    noDts: true
933                });
934
935                if (withRefs && !disableSourceOfProjectReferenceRedirect) {
936                    verifyScenarioWithChanges({
937                        scenarioName: "when defining project source changes",
938                        verifier,
939                        withRefs,
940                        change: (host, session, verifiers) => {
941                            // Make change, without rebuild of solution
942                            if (contains(openFiles(verifiers), dependencyTs)) {
943                                session.executeCommandSeq<protocol.ChangeRequest>({
944                                    command: protocol.CommandTypes.Change,
945                                    arguments: {
946                                        file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { }
947`}
948                                });
949                            }
950                            else {
951                                host.writeFile(dependencyTs.path, `function fooBar() { }
952${dependencyTs.content}`);
953                            }
954                        },
955                        afterChangeActionKey: "dependencyChange"
956                    });
957
958                    it("when projects are not built", () => {
959                        const host = createServerHost(files);
960                        const session = createSession(host);
961                        const verifiers = verifier(withRefs);
962                        openFilesForSession([...openFiles(verifiers), randomFile], session);
963                        verifyScenarioAndScriptInfoCollection(session, host, verifiers, "noBuild");
964                    });
965                }
966            }
967
968            interface VerifyScenario {
969                mainScenario: string;
970                verifier: (withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) => readonly DocumentPositionMapperVerifier[];
971            }
972            function verifyScenario(scenario: VerifyScenario) {
973                describe(scenario.mainScenario, () => {
974                    describe("when main tsconfig doesnt have project reference", () => {
975                        verifyScenarioWorker(scenario, /*withRefs*/ false);
976                    });
977                    describe("when main tsconfig has project reference", () => {
978                        verifyScenarioWorker(scenario, /*withRefs*/ true);
979                    });
980                    describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => {
981                        verifyScenarioWorker(scenario, /*withRefs*/ true, /*disableSourceOfProjectReferenceRedirect*/ true);
982                    });
983                });
984            }
985
986            describe("from project that uses dependency", () => {
987                verifyScenario({
988                    mainScenario: "can go to definition correctly",
989                    verifier: (withRefs, disableSourceOfProjectReferenceRedirect) => [
990                        {
991                            ...goToDefFromMainTsProjectInfoVerifier(withRefs),
992                            main: () => ({
993                                action: goToDefFromMainTs,
994                                closedInfos: withRefs ?
995                                    [dependencyTs.path, dependencyConfig.path, libFile.path] :
996                                    disableSourceOfProjectReferenceRedirect ?
997                                        [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] :
998                                        [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation],
999                                otherWatchedFiles: [mainConfig.path],
1000                                expectsDts: !withRefs, // Dts script info present only if no project reference
1001                                expectsMap: !withRefs // Map script info present only if no project reference
1002                            }),
1003                            change: "main",
1004                            dtsChange: "main",
1005                            mapChange: ["main", () => ({
1006                                freshDocumentMapper: true
1007                            })],
1008                            noMap: withRefs ?
1009                                "main" :
1010                                ["main", main => ({
1011                                    action: goToDefFromMainTsWithNoMap,
1012                                    // Because map is deleted, dts and dependency are released
1013                                    closedInfos: removePath(main.closedInfos, dtsMapPath, dependencyTsPath),
1014                                    // Watches deleted file
1015                                    otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation),
1016                                    expectsMap: false
1017                                })],
1018                            mapFileCreated: "main",
1019                            mapFileDeleted: withRefs ?
1020                                "main" :
1021                                ["noMap", noMap => ({
1022                                    // The script info for depedency is collected only after file open
1023                                    closedInfos: noMap.closedInfos.concat(dependencyTs.path)
1024                                })],
1025                            noDts: withRefs ?
1026                                "main" :
1027                                ["main", main => ({
1028                                    action: goToDefFromMainTsWithNoDts,
1029                                    // No dts, no map, no dependency
1030                                    closedInfos: removePath(main.closedInfos, dtsPath, dtsMapPath, dependencyTsPath),
1031                                    expectsDts: false,
1032                                    expectsMap: false
1033                                })],
1034                            dtsFileCreated: "main",
1035                            dtsFileDeleted: withRefs ?
1036                                "main" :
1037                                ["noDts", noDts => ({
1038                                    // The script info for map is collected only after file open
1039                                    closedInfos: noDts.closedInfos.concat(dependencyTs.path, dtsMapLocation),
1040                                    expectsMap: true
1041                                })],
1042                            dependencyChange: ["main", () => ({
1043                                action: goToDefFromMainTsWithDependencyChange,
1044                            })],
1045                            noBuild: "noDts"
1046                        }
1047                    ]
1048                });
1049            });
1050
1051            describe("from defining project", () => {
1052                verifyScenario({
1053                    mainScenario: "rename locations from dependency",
1054                    verifier: () => [
1055                        {
1056                            ...renameFromDependencyTsProjectInfoVerifier(),
1057                            main: () => ({
1058                                action: renameFromDependencyTs,
1059                                closedInfos: [libFile.path, dtsLocation, dtsMapLocation],
1060                                otherWatchedFiles: [dependencyConfig.path],
1061                                expectsDts: true,
1062                                expectsMap: true
1063                            }),
1064                            change: "main",
1065                            dtsChange: "main",
1066                            mapChange: ["main", () => ({
1067                                freshDocumentMapper: true
1068                            })],
1069                            noMap: ["main", main => ({
1070                                // No map
1071                                closedInfos: removePath(main.closedInfos, dtsMapPath),
1072                                // watch map
1073                                otherWatchedFiles: [...main.otherWatchedFiles, dtsMapLocation],
1074                                expectsMap: false
1075                            })],
1076                            mapFileCreated: "main",
1077                            mapFileDeleted: "noMap",
1078                            noDts: ["main", main => ({
1079                                // no dts or map since dts itself doesnt exist
1080                                closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath),
1081                                // watch deleted file
1082                                otherWatchedFiles: [...main.otherWatchedFiles, dtsLocation],
1083                                expectsDts: false,
1084                                expectsMap: false
1085                            })],
1086                            dtsFileCreated: "main",
1087                            dtsFileDeleted: ["noDts", noDts => ({
1088                                // Map is collected after file open
1089                                closedInfos: noDts.closedInfos.concat(dtsMapLocation),
1090                                expectsMap: true
1091                            })],
1092                            dependencyChange: ["main", () => ({
1093                                action: renameFromDependencyTsWithDependencyChange
1094                            })],
1095                            noBuild: "noDts"
1096                        }
1097                    ]
1098                });
1099            });
1100
1101            describe("when opening depedency and usage project", () => {
1102                verifyScenario({
1103                    mainScenario: "goto Definition in usage and rename locations from defining project",
1104                    verifier: (withRefs, disableSourceOfProjectReferenceRedirect) => [
1105                        {
1106                            ...goToDefFromMainTsProjectInfoVerifier(withRefs),
1107                            main: () => ({
1108                                action: goToDefFromMainTs,
1109                                // DependencyTs is open, so omit it from closed infos
1110                                closedInfos: withRefs ?
1111                                    [dependencyConfig.path, libFile.path] :
1112                                    disableSourceOfProjectReferenceRedirect ?
1113                                        [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] :
1114                                        [libFile.path, dtsPath, dtsMapLocation],
1115                                otherWatchedFiles: withRefs || disableSourceOfProjectReferenceRedirect ?
1116                                    [mainConfig.path] : // dependencyConfig is in closed info
1117                                    [mainConfig.path, dependencyConfig.path],
1118                                expectsDts: !withRefs, // Dts script info present only if no project reference
1119                                expectsMap: !withRefs // Map script info present only if no project reference
1120                            }),
1121                            change: withRefs ?
1122                                ["main", main => ({
1123                                    // Because before this rename is done the closed info remains same as rename's main operation
1124                                    closedInfos: main.closedInfos.concat(dtsLocation, dtsMapLocation),
1125                                    expectsDts: true,
1126                                    expectsMap: true
1127                                })] :
1128                                "main",
1129                            dtsChange: "change",
1130                            mapChange: "change",
1131                            noMap: withRefs ?
1132                                "main" :
1133                                ["main", main => ({
1134                                    action: goToDefFromMainTsWithNoMap,
1135                                    closedInfos: removePath(main.closedInfos, dtsMapPath),
1136                                    otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation),
1137                                    expectsMap: false
1138                                })],
1139                            mapFileCreated: withRefs ?
1140                                ["main", main => ({
1141                                    // Because before this rename is done the closed info remains same as rename's main
1142                                    closedInfos: main.closedInfos.concat(dtsLocation),
1143                                    expectsDts: true,
1144                                    // This operation doesnt need map so the map info path in dts is not refreshed
1145                                    skipDtsMapCheck: withRefs
1146                                })] :
1147                                "main",
1148                            mapFileDeleted: withRefs ?
1149                                ["noMap", noMap => ({
1150                                    // Because before this rename is done the closed info remains same as rename's noMap operation
1151                                    closedInfos: noMap.closedInfos.concat(dtsLocation),
1152                                    expectsDts: true,
1153                                    // This operation doesnt need map so the map info path in dts is not refreshed
1154                                    skipDtsMapCheck: true
1155                                })] :
1156                                "noMap",
1157                            noDts: withRefs ?
1158                                "main" :
1159                                ["main", main => ({
1160                                    action: goToDefFromMainTsWithNoDts,
1161                                    closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath),
1162                                    expectsDts: false,
1163                                    expectsMap: false
1164                                })],
1165                            dtsFileCreated: withRefs ?
1166                                ["main", main => ({
1167                                    // Since the project for dependency is not updated, the watcher from rename for dts still there
1168                                    otherWatchedFiles: main.otherWatchedFiles.concat(dtsLocation)
1169                                })] :
1170                                "main",
1171                            dtsFileDeleted: ["noDts", noDts => ({
1172                                // Map collection after file open
1173                                closedInfos: noDts.closedInfos.concat(dtsMapLocation),
1174                                expectsMap: true
1175                            })],
1176                            dependencyChange: ["change", () => ({
1177                                action: goToDefFromMainTsWithDependencyChange,
1178                            })],
1179                            noBuild: "noDts"
1180                        },
1181                        {
1182                            ...renameFromDependencyTsProjectInfoVerifier(),
1183                            main: () => ({
1184                                action: renameFromDependencyTsWithBothProjectsOpen,
1185                                // DependencyTs is open, so omit it from closed infos
1186                                closedInfos: withRefs ?
1187                                    [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] :
1188                                    disableSourceOfProjectReferenceRedirect ?
1189                                        [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] :
1190                                        [libFile.path, dtsPath, dtsMapLocation],
1191                                otherWatchedFiles: withRefs || disableSourceOfProjectReferenceRedirect ?
1192                                    [mainConfig.path] : // dependencyConfig is in closed info
1193                                    [mainConfig.path, dependencyConfig.path],
1194                                expectsDts: true,
1195                                expectsMap: true,
1196                                freshMapInfo: withRefs
1197                            }),
1198                            change: ["main", () => ({
1199                                freshMapInfo: false
1200                            })],
1201                            dtsChange: "change",
1202                            mapChange: ["main", () => ({
1203                                freshMapInfo: false,
1204                                freshDocumentMapper: withRefs
1205                            })],
1206                            noMap: ["main", main => ({
1207                                action: withRefs ?
1208                                    renameFromDependencyTsWithBothProjectsOpen :
1209                                    renameFromDependencyTs,
1210                                closedInfos: removePath(main.closedInfos, dtsMapPath),
1211                                otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation),
1212                                expectsMap: false,
1213                                freshDocumentMapper: withRefs
1214                            })],
1215                            mapFileCreated: "main",
1216                            mapFileDeleted: "noMap",
1217                            noDts: ["change", change => ({
1218                                action: withRefs ?
1219                                    renameFromDependencyTsWithBothProjectsOpen :
1220                                    renameFromDependencyTs,
1221                                closedInfos: removePath(change.closedInfos, dtsPath, dtsMapPath),
1222                                otherWatchedFiles: change.otherWatchedFiles.concat(dtsLocation),
1223                                expectsDts: false,
1224                                expectsMap: false
1225                            })],
1226                            dtsFileCreated: "main",
1227                            dtsFileDeleted: ["noDts", noDts => ({
1228                                // Map collection after file open
1229                                closedInfos: noDts.closedInfos.concat(dtsMapLocation),
1230                                expectsMap: true
1231                            })],
1232                            dependencyChange: ["change", () => ({
1233                                action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange
1234                            })],
1235                            noBuild: "noDts"
1236                        }
1237                    ]
1238                });
1239            });
1240        });
1241
1242        describe("when root file is file from referenced project", () => {
1243            function verify(disableSourceOfProjectReferenceRedirect: boolean) {
1244                const projectLocation = `/user/username/projects/project`;
1245                const commonConfig: File = {
1246                    path: `${projectLocation}/src/common/tsconfig.json`,
1247                    content: JSON.stringify({
1248                        compilerOptions: {
1249                            composite: true,
1250                            declarationMap: true,
1251                            outDir: "../../out",
1252                            baseUrl: "..",
1253                            disableSourceOfProjectReferenceRedirect
1254                        },
1255                        include: ["./**/*"]
1256                    })
1257                };
1258                const keyboardTs: File = {
1259                    path: `${projectLocation}/src/common/input/keyboard.ts`,
1260                    content: `function bar() { return "just a random function so .d.ts location doesnt match"; }
1261export function evaluateKeyboardEvent() { }`
1262                };
1263                const keyboardTestTs: File = {
1264                    path: `${projectLocation}/src/common/input/keyboard.test.ts`,
1265                    content: `import { evaluateKeyboardEvent } from 'common/input/keyboard';
1266function testEvaluateKeyboardEvent() {
1267    return evaluateKeyboardEvent();
1268}
1269`
1270                };
1271                const srcConfig: File = {
1272                    path: `${projectLocation}/src/tsconfig.json`,
1273                    content: JSON.stringify({
1274                        compilerOptions: {
1275                            composite: true,
1276                            declarationMap: true,
1277                            outDir: "../out",
1278                            baseUrl: ".",
1279                            paths: {
1280                                "common/*": ["./common/*"],
1281                            },
1282                            tsBuildInfoFile: "../out/src.tsconfig.tsbuildinfo",
1283                            disableSourceOfProjectReferenceRedirect
1284                        },
1285                        include: ["./**/*"],
1286                        references: [
1287                            { path: "./common" }
1288                        ]
1289                    })
1290                };
1291                const terminalTs: File = {
1292                    path: `${projectLocation}/src/terminal.ts`,
1293                    content: `import { evaluateKeyboardEvent } from 'common/input/keyboard';
1294function foo() {
1295    return evaluateKeyboardEvent();
1296}
1297`
1298                };
1299                const host = createHost(
1300                    [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile],
1301                    [srcConfig.path]
1302                );
1303                const session = createSession(host);
1304                openFilesForSession([keyboardTs, terminalTs], session);
1305
1306                const searchStr = "evaluateKeyboardEvent";
1307                const importStr = `import { evaluateKeyboardEvent } from 'common/input/keyboard';`;
1308                const result = session.executeCommandSeq<protocol.ReferencesRequest>({
1309                    command: protocol.CommandTypes.References,
1310                    arguments: protocolFileLocationFromSubstring(keyboardTs, searchStr)
1311                }).response as protocol.ReferencesResponseBody;
1312                assert.deepEqual(result, {
1313                    refs: [
1314                        makeReferenceItem({
1315                            file: keyboardTs,
1316                            text: searchStr,
1317                            contextText: `export function evaluateKeyboardEvent() { }`,
1318                            isDefinition: true,
1319                            lineText: `export function evaluateKeyboardEvent() { }`
1320                        }),
1321                        makeReferenceItem({
1322                            file: keyboardTestTs,
1323                            text: searchStr,
1324                            contextText: importStr,
1325                            isDefinition: true,
1326                            lineText: importStr
1327                        }),
1328                        makeReferenceItem({
1329                            file: keyboardTestTs,
1330                            text: searchStr,
1331                            options: { index: 1 },
1332                            isDefinition: false,
1333                            lineText: `    return evaluateKeyboardEvent();`
1334                        }),
1335                        makeReferenceItem({
1336                            file: terminalTs,
1337                            text: searchStr,
1338                            contextText: importStr,
1339                            isDefinition: true,
1340                            lineText: importStr
1341                        }),
1342                        makeReferenceItem({
1343                            file: terminalTs,
1344                            text: searchStr,
1345                            options: { index: 1 },
1346                            isDefinition: false,
1347                            lineText: `    return evaluateKeyboardEvent();`
1348                        }),
1349                    ],
1350                    symbolName: searchStr,
1351                    symbolStartOffset: protocolLocationFromSubstring(keyboardTs.content, searchStr).offset,
1352                    symbolDisplayString: "function evaluateKeyboardEvent(): void"
1353                });
1354            }
1355
1356            it(`when using declaration file maps to navigate between projects`, () => {
1357                verify(/*disableSourceOfProjectReferenceRedirect*/ true);
1358            });
1359            it(`when using original source files in the project`, () => {
1360                verify(/*disableSourceOfProjectReferenceRedirect*/ false);
1361            });
1362        });
1363
1364        it("reusing d.ts files from composite and non composite projects", () => {
1365            const configA: File = {
1366                path: `${tscWatch.projectRoot}/compositea/tsconfig.json`,
1367                content: JSON.stringify({
1368                    compilerOptions: {
1369                        composite: true,
1370                        outDir: "../dist/",
1371                        rootDir: "../",
1372                        baseUrl: "../",
1373                        paths: { "@ref/*": ["./dist/*"] }
1374                    }
1375                })
1376            };
1377            const aTs: File = {
1378                path: `${tscWatch.projectRoot}/compositea/a.ts`,
1379                content: `import { b } from "@ref/compositeb/b";`
1380            };
1381            const a2Ts: File = {
1382                path: `${tscWatch.projectRoot}/compositea/a2.ts`,
1383                content: `export const x = 10;`
1384            };
1385            const configB: File = {
1386                path: `${tscWatch.projectRoot}/compositeb/tsconfig.json`,
1387                content: configA.content
1388            };
1389            const bTs: File = {
1390                path: `${tscWatch.projectRoot}/compositeb/b.ts`,
1391                content: "export function b() {}"
1392            };
1393            const bDts: File = {
1394                path: `${tscWatch.projectRoot}/dist/compositeb/b.d.ts`,
1395                content: "export declare function b(): void;"
1396            };
1397            const configC: File = {
1398                path: `${tscWatch.projectRoot}/compositec/tsconfig.json`,
1399                content: JSON.stringify({
1400                    compilerOptions: {
1401                        composite: true,
1402                        outDir: "../dist/",
1403                        rootDir: "../",
1404                        baseUrl: "../",
1405                        paths: { "@ref/*": ["./*"] }
1406                    },
1407                    references: [{ path: "../compositeb" }]
1408                })
1409            };
1410            const cTs: File = {
1411                path: `${tscWatch.projectRoot}/compositec/c.ts`,
1412                content: aTs.content
1413            };
1414            const files = [libFile, aTs, a2Ts, configA, bDts, bTs, configB, cTs, configC];
1415            const host = createServerHost(files);
1416            const service = createProjectService(host);
1417            service.openClientFile(aTs.path);
1418            service.checkNumberOfProjects({ configuredProjects: 1 });
1419
1420            // project A referencing b.d.ts without project reference
1421            const projectA = service.configuredProjects.get(configA.path)!;
1422            assert.isDefined(projectA);
1423            checkProjectActualFiles(projectA, [aTs.path, a2Ts.path, bDts.path, libFile.path, configA.path]);
1424
1425            // reuses b.d.ts but sets the path and resolved path since projectC has project references
1426            // as the real resolution was to b.ts
1427            service.openClientFile(cTs.path);
1428            service.checkNumberOfProjects({ configuredProjects: 2 });
1429            const projectC = service.configuredProjects.get(configC.path)!;
1430            checkProjectActualFiles(projectC, [cTs.path, bTs.path, libFile.path, configC.path]);
1431
1432            // Now new project for project A tries to reuse b but there is no filesByName mapping for b's source location
1433            host.writeFile(a2Ts.path, `${a2Ts.content}export const y = 30;`);
1434            assert.isTrue(projectA.dirty);
1435            projectA.updateGraph();
1436        });
1437
1438        describe("when references are monorepo like with symlinks", () => {
1439            interface Packages {
1440                bPackageJson: File;
1441                aTest: File;
1442                bFoo: File;
1443                bBar: File;
1444                bSymlink: SymLink;
1445            }
1446            function verifySymlinkScenario(packages: () => Packages) {
1447                describe("when solution is not built", () => {
1448                    it("with preserveSymlinks turned off", () => {
1449                        verifySession(packages(), /*alreadyBuilt*/ false, {});
1450                    });
1451
1452                    it("with preserveSymlinks turned on", () => {
1453                        verifySession(packages(), /*alreadyBuilt*/ false, { preserveSymlinks: true });
1454                    });
1455                });
1456
1457                describe("when solution is already built", () => {
1458                    it("with preserveSymlinks turned off", () => {
1459                        verifySession(packages(), /*alreadyBuilt*/ true, {});
1460                    });
1461
1462                    it("with preserveSymlinks turned on", () => {
1463                        verifySession(packages(), /*alreadyBuilt*/ true, { preserveSymlinks: true });
1464                    });
1465                });
1466            }
1467
1468            function verifySession({ bPackageJson, aTest, bFoo, bBar, bSymlink }: Packages, alreadyBuilt: boolean, extraOptions: CompilerOptions) {
1469                const aConfig = config("A", extraOptions, ["../B"]);
1470                const bConfig = config("B", extraOptions);
1471                const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink];
1472                const host = alreadyBuilt ?
1473                    createHost(files, [aConfig.path]) :
1474                    createServerHost(files);
1475
1476                // Create symlink in node module
1477                const session = createSession(host, { canUseEvents: true });
1478                openFilesForSession([aTest], session);
1479                const service = session.getProjectService();
1480                const project = service.configuredProjects.get(aConfig.path.toLowerCase())!;
1481                assert.deepEqual(project.getAllProjectErrors(), []);
1482                checkProjectActualFiles(
1483                    project,
1484                    [aConfig.path, aTest.path, bFoo.path, bBar.path, libFile.path]
1485                );
1486                verifyGetErrRequestNoErrors({ session, host, files: [aTest] });
1487                session.executeCommandSeq<protocol.UpdateOpenRequest>({
1488                    command: protocol.CommandTypes.UpdateOpen,
1489                    arguments: {
1490                        changedFiles: [{
1491                            fileName: aTest.path,
1492                            textChanges: [{
1493                                newText: "\n",
1494                                start: { line: 5, offset: 1 },
1495                                end: { line: 5, offset: 1 }
1496                            }]
1497                        }]
1498                    }
1499                });
1500                verifyGetErrRequestNoErrors({ session, host, files: [aTest] });
1501            }
1502
1503            function config(packageName: string, extraOptions: CompilerOptions, references?: string[]): File {
1504                return {
1505                    path: `${tscWatch.projectRoot}/packages/${packageName}/tsconfig.json`,
1506                    content: JSON.stringify({
1507                        compilerOptions: {
1508                            outDir: "lib",
1509                            rootDir: "src",
1510                            composite: true,
1511                            ...extraOptions
1512                        },
1513                        include: ["src"],
1514                        ...(references ? { references: references.map(path => ({ path })) } : {})
1515                    })
1516                };
1517            }
1518
1519            function file(packageName: string, fileName: string, content: string): File {
1520                return {
1521                    path: `${tscWatch.projectRoot}/packages/${packageName}/src/${fileName}`,
1522                    content
1523                };
1524            }
1525
1526            function verifyMonoRepoLike(scope = "") {
1527                describe("when packageJson has types field and has index.ts", () => {
1528                    verifySymlinkScenario(() => ({
1529                        bPackageJson: {
1530                            path: `${tscWatch.projectRoot}/packages/B/package.json`,
1531                            content: JSON.stringify({
1532                                main: "lib/index.js",
1533                                types: "lib/index.d.ts"
1534                            })
1535                        },
1536                        aTest: file("A", "index.ts", `import { foo } from '${scope}b';
1537import { bar } from '${scope}b/lib/bar';
1538foo();
1539bar();
1540`),
1541                        bFoo: file("B", "index.ts", `export function foo() { }`),
1542                        bBar: file("B", "bar.ts", `export function bar() { }`),
1543                        bSymlink: {
1544                            path: `${tscWatch.projectRoot}/node_modules/${scope}b`,
1545                            symLink: `${tscWatch.projectRoot}/packages/B`
1546                        }
1547                    }));
1548                });
1549
1550                describe("when referencing file from subFolder", () => {
1551                    verifySymlinkScenario(() => ({
1552                        bPackageJson: {
1553                            path: `${tscWatch.projectRoot}/packages/B/package.json`,
1554                            content: "{}"
1555                        },
1556                        aTest: file("A", "test.ts", `import { foo } from '${scope}b/lib/foo';
1557import { bar } from '${scope}b/lib/bar/foo';
1558foo();
1559bar();
1560`),
1561                        bFoo: file("B", "foo.ts", `export function foo() { }`),
1562                        bBar: file("B", "bar/foo.ts", `export function bar() { }`),
1563                        bSymlink: {
1564                            path: `${tscWatch.projectRoot}/node_modules/${scope}b`,
1565                            symLink: `${tscWatch.projectRoot}/packages/B`
1566                        }
1567                    }));
1568                });
1569            }
1570            describe("when package is not scoped", () => {
1571                verifyMonoRepoLike();
1572            });
1573            describe("when package is scoped", () => {
1574                verifyMonoRepoLike("@issue/");
1575            });
1576        });
1577
1578        it("when finding local reference doesnt load ancestor/sibling projects", () => {
1579            const solutionLocation = "/user/username/projects/solution";
1580            const solution: File = {
1581                path: `${solutionLocation}/tsconfig.json`,
1582                content: JSON.stringify({
1583                    files: [],
1584                    include: [],
1585                    references: [
1586                        { path: "./compiler" },
1587                        { path: "./services" },
1588                    ]
1589                })
1590            };
1591            const compilerConfig: File = {
1592                path: `${solutionLocation}/compiler/tsconfig.json`,
1593                content: JSON.stringify({
1594                    compilerOptions: {
1595                        composite: true,
1596                        module: "none"
1597                    },
1598                    files: ["./types.ts", "./program.ts"]
1599                })
1600            };
1601            const typesFile: File = {
1602                path: `${solutionLocation}/compiler/types.ts`,
1603                content: `
1604                namespace ts {
1605                    export interface Program {
1606                        getSourceFiles(): string[];
1607                    }
1608                }`
1609            };
1610            const programFile: File = {
1611                path: `${solutionLocation}/compiler/program.ts`,
1612                content: `
1613                namespace ts {
1614                    export const program: Program = {
1615                        getSourceFiles: () => [getSourceFile()]
1616                    };
1617                    function getSourceFile() { return "something"; }
1618                }`
1619            };
1620            const servicesConfig: File = {
1621                path: `${solutionLocation}/services/tsconfig.json`,
1622                content: JSON.stringify({
1623                    compilerOptions: {
1624                        composite: true
1625                    },
1626                    files: ["./services.ts"],
1627                    references: [
1628                        { path: "../compiler" }
1629                    ]
1630                })
1631            };
1632            const servicesFile: File = {
1633                path: `${solutionLocation}/services/services.ts`,
1634                content: `
1635                namespace ts {
1636                    const result = program.getSourceFiles();
1637                }`
1638            };
1639
1640            const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile];
1641            const host = createServerHost(files);
1642            const session = createSession(host);
1643            const service = session.getProjectService();
1644            service.openClientFile(programFile.path);
1645            checkNumberOfProjects(service, { configuredProjects: 2 });
1646            const compilerProject = service.configuredProjects.get(compilerConfig.path)!;
1647            checkProjectActualFiles(compilerProject, [libFile.path, typesFile.path, programFile.path, compilerConfig.path]);
1648            const solutionProject = service.configuredProjects.get(solution.path)!;
1649            assert.isTrue(solutionProject.isInitialLoadPending());
1650
1651            // Find all references for getSourceFile
1652            const response = session.executeCommandSeq<protocol.ReferencesRequest>({
1653                command: protocol.CommandTypes.References,
1654                arguments: protocolFileLocationFromSubstring(programFile, "getSourceFile", { index: 1 })
1655            }).response as protocol.ReferencesResponseBody;
1656            assert.deepEqual(response, {
1657                refs: [
1658                    makeReferenceItem({
1659                        file: programFile,
1660                        text: "getSourceFile",
1661                        options: { index: 1 },
1662                        isDefinition: false,
1663                        lineText: `                        getSourceFiles: () => [getSourceFile()]`,
1664                    }),
1665                    makeReferenceItem({
1666                        file: programFile,
1667                        text: "getSourceFile",
1668                        options: { index: 2 },
1669                        contextText: `function getSourceFile() { return "something"; }`,
1670                        isDefinition: true,
1671                        lineText: `                    function getSourceFile() { return "something"; }`,
1672                    })
1673                ],
1674                symbolName: "getSourceFile",
1675                symbolStartOffset: protocolLocationFromSubstring(programFile.content, "getSourceFile", { index: 1 }).offset,
1676                symbolDisplayString: "function getSourceFile(): string"
1677            });
1678            // Shouldnt load more projects
1679            checkNumberOfProjects(service, { configuredProjects: 2 });
1680            assert.isTrue(solutionProject.isInitialLoadPending());
1681
1682            // Find all references for getSourceFiles
1683            const getSourceFilesResponse = session.executeCommandSeq<protocol.ReferencesRequest>({
1684                command: protocol.CommandTypes.References,
1685                arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles")
1686            }).response as protocol.ReferencesResponseBody;
1687            assert.deepEqual(getSourceFilesResponse, {
1688                refs: [
1689                    makeReferenceItem({
1690                        file: typesFile,
1691                        text: "getSourceFiles",
1692                        contextText: `getSourceFiles(): string[];`,
1693                        isDefinition: true,
1694                        isWriteAccess: false,
1695                        lineText: `                        getSourceFiles(): string[];`,
1696                    }),
1697                    makeReferenceItem({
1698                        file: programFile,
1699                        text: "getSourceFiles",
1700                        contextText: `getSourceFiles: () => [getSourceFile()]`,
1701                        isDefinition: true,
1702                        lineText: `                        getSourceFiles: () => [getSourceFile()]`,
1703                    }),
1704                    makeReferenceItem({
1705                        file: servicesFile,
1706                        text: "getSourceFiles",
1707                        isDefinition: false,
1708                        lineText: `                    const result = program.getSourceFiles();`,
1709                    })
1710                ],
1711                symbolName: "getSourceFiles",
1712                symbolStartOffset: protocolLocationFromSubstring(typesFile.content, "getSourceFiles").offset,
1713                symbolDisplayString: "(method) ts.Program.getSourceFiles(): string[]"
1714            });
1715
1716            // Should load more projects
1717            checkNumberOfProjects(service, { configuredProjects: 3 });
1718            assert.isFalse(solutionProject.isInitialLoadPending());
1719            checkProjectActualFiles(solutionProject, [solution.path]);
1720            checkProjectActualFiles(service.configuredProjects.get(servicesConfig.path)!, [servicesFile.path, servicesConfig.path, libFile.path, typesFile.path, programFile.path]);
1721        });
1722
1723        it("when disableSolutionSearching is true, solution and siblings are not loaded", () => {
1724            const solutionLocation = "/user/username/projects/solution";
1725            const solution: File = {
1726                path: `${solutionLocation}/tsconfig.json`,
1727                content: JSON.stringify({
1728                    files: [],
1729                    include: [],
1730                    references: [
1731                        { path: "./compiler" },
1732                        { path: "./services" },
1733                    ]
1734                })
1735            };
1736            const compilerConfig: File = {
1737                path: `${solutionLocation}/compiler/tsconfig.json`,
1738                content: JSON.stringify({
1739                    compilerOptions: {
1740                        composite: true,
1741                        module: "none",
1742                        disableSolutionSearching: true
1743                    },
1744                    files: ["./types.ts", "./program.ts"]
1745                })
1746            };
1747            const typesFile: File = {
1748                path: `${solutionLocation}/compiler/types.ts`,
1749                content: `
1750                namespace ts {
1751                    export interface Program {
1752                        getSourceFiles(): string[];
1753                    }
1754                }`
1755            };
1756            const programFile: File = {
1757                path: `${solutionLocation}/compiler/program.ts`,
1758                content: `
1759                namespace ts {
1760                    export const program: Program = {
1761                        getSourceFiles: () => [getSourceFile()]
1762                    };
1763                    function getSourceFile() { return "something"; }
1764                }`
1765            };
1766            const servicesConfig: File = {
1767                path: `${solutionLocation}/services/tsconfig.json`,
1768                content: JSON.stringify({
1769                    compilerOptions: {
1770                        composite: true
1771                    },
1772                    files: ["./services.ts"],
1773                    references: [
1774                        { path: "../compiler" }
1775                    ]
1776                })
1777            };
1778            const servicesFile: File = {
1779                path: `${solutionLocation}/services/services.ts`,
1780                content: `
1781                namespace ts {
1782                    const result = program.getSourceFiles();
1783                }`
1784            };
1785
1786            const files = [libFile, solution, compilerConfig, typesFile, programFile, servicesConfig, servicesFile, libFile];
1787            const host = createServerHost(files);
1788            const session = createSession(host);
1789            const service = session.getProjectService();
1790            service.openClientFile(programFile.path);
1791            checkNumberOfProjects(service, { configuredProjects: 1 });
1792            const compilerProject = service.configuredProjects.get(compilerConfig.path)!;
1793            checkProjectActualFiles(compilerProject, [libFile.path, typesFile.path, programFile.path, compilerConfig.path]);
1794
1795            // Find all references
1796            const getSourceFilesResponse = session.executeCommandSeq<protocol.ReferencesRequest>({
1797                command: protocol.CommandTypes.References,
1798                arguments: protocolFileLocationFromSubstring(programFile, "getSourceFiles")
1799            }).response as protocol.ReferencesResponseBody;
1800            assert.deepEqual(getSourceFilesResponse, {
1801                refs: [
1802                    makeReferenceItem({
1803                        file: typesFile,
1804                        text: "getSourceFiles",
1805                        contextText: `getSourceFiles(): string[];`,
1806                        isDefinition: true,
1807                        isWriteAccess: false,
1808                        lineText: `                        getSourceFiles(): string[];`,
1809                    }),
1810                    makeReferenceItem({
1811                        file: programFile,
1812                        text: "getSourceFiles",
1813                        contextText: `getSourceFiles: () => [getSourceFile()]`,
1814                        isDefinition: true,
1815                        lineText: `                        getSourceFiles: () => [getSourceFile()]`,
1816                    }),
1817                ],
1818                symbolName: "getSourceFiles",
1819                symbolStartOffset: protocolLocationFromSubstring(typesFile.content, "getSourceFiles").offset,
1820                symbolDisplayString: "(method) ts.Program.getSourceFiles(): string[]"
1821            });
1822
1823            // No new solutions/projects loaded
1824            checkNumberOfProjects(service, { configuredProjects: 1 });
1825        });
1826
1827        describe("when default project is solution project", () => {
1828            interface Setup {
1829                solutionOptions?: CompilerOptions;
1830                solutionFiles?: string[];
1831                configRefs: string[];
1832                additionalFiles: readonly File[];
1833                expectedOpenEvents: protocol.Event[];
1834            }
1835            interface VerifySolutionScenario extends Setup {
1836                solutionProject?: readonly string[];
1837                additionalProjects: readonly { projectName: string, files: readonly string[] }[];
1838                expectedReloadEvents: protocol.Event[];
1839                expectedReferences: protocol.ReferencesResponseBody;
1840                expectedReferencesFromDtsProject: protocol.ReferencesResponseBody;
1841            }
1842            const main: File = {
1843                path: `${tscWatch.projectRoot}/src/main.ts`,
1844                content: `import { foo } from 'helpers/functions';
1845export { foo };`
1846            };
1847            const helper: File = {
1848                path: `${tscWatch.projectRoot}/src/helpers/functions.ts`,
1849                content: `export const foo = 1;`
1850            };
1851            const mainDts: File = {
1852                path: `${tscWatch.projectRoot}/target/src/main.d.ts`,
1853                content: `import { foo } from 'helpers/functions';
1854export { foo };
1855//# sourceMappingURL=main.d.ts.map`
1856            };
1857            const mainDtsMap: File = {
1858                path: `${tscWatch.projectRoot}/target/src/main.d.ts.map`,
1859                content: `{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAExC,OAAO,EAAC,GAAG,EAAC,CAAC"}`
1860            };
1861            const helperDts: File = {
1862                path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts`,
1863                content: `export declare const foo = 1;
1864//# sourceMappingURL=functions.d.ts.map`
1865            };
1866            const helperDtsMap: File = {
1867                path: `${tscWatch.projectRoot}/target/src/helpers/functions.d.ts.map`,
1868                content: `{"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../src/helpers/functions.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,IAAI,CAAC"}`
1869            };
1870            const tsconfigIndirect3: File = {
1871                path: `${tscWatch.projectRoot}/indirect3/tsconfig.json`,
1872                content: JSON.stringify({
1873                    compilerOptions: {
1874                        baseUrl: "../target/src/"
1875                    },
1876                })
1877            };
1878            const fileResolvingToMainDts: File = {
1879                path: `${tscWatch.projectRoot}/indirect3/main.ts`,
1880                content: `import { foo } from 'main';
1881foo;
1882export function bar() {}`
1883            };
1884            const tsconfigSrcPath = `${tscWatch.projectRoot}/tsconfig-src.json`;
1885            const tsconfigPath = `${tscWatch.projectRoot}/tsconfig.json`;
1886            const dummyFilePath = "/dummy/dummy.ts";
1887            function setup({ solutionFiles, solutionOptions, configRefs, additionalFiles, expectedOpenEvents }: Setup) {
1888                const tsconfigSrc: File = {
1889                    path: tsconfigSrcPath,
1890                    content: JSON.stringify({
1891                        compilerOptions: {
1892                            composite: true,
1893                            outDir: "./target/",
1894                            baseUrl: "./src/"
1895                        },
1896                        include: ["./src/**/*"]
1897                    })
1898                };
1899                const tsconfig: File = {
1900                    path: tsconfigPath,
1901                    content: JSON.stringify({
1902                        ... (solutionOptions ? { compilerOptions: solutionOptions } : {}),
1903                        references: configRefs.map(path => ({ path })),
1904                        files: solutionFiles || []
1905                    })
1906                };
1907                const dummyFile: File = {
1908                    path: dummyFilePath,
1909                    content: "let a = 10;"
1910                };
1911                const host = createServerHost([
1912                    tsconfigSrc, tsconfig, main, helper,
1913                    libFile, dummyFile,
1914                    mainDts, mainDtsMap, helperDts, helperDtsMap,
1915                    tsconfigIndirect3, fileResolvingToMainDts,
1916                    ...additionalFiles]);
1917                const session = createSession(host, { canUseEvents: true });
1918                const service = session.getProjectService();
1919                service.openClientFile(main.path);
1920                checkEvents(session, expectedOpenEvents);
1921                return { session, service, host, tsconfigSrc, tsconfig };
1922            }
1923
1924            function verifySolutionScenario(input: VerifySolutionScenario) {
1925                const { session, service, host, tsconfigSrc, tsconfig } = setup(input);
1926                const {
1927                    solutionProject, additionalProjects, expectedReloadEvents,
1928                    expectedReferences, expectedReferencesFromDtsProject
1929                } = input;
1930                verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ false);
1931                const info = service.getScriptInfoForPath(main.path as Path)!;
1932                const project = service.configuredProjects.get(tsconfigSrc.path)!;
1933                assert.equal(info.getDefaultProject(), project);
1934                assert.equal(service.findDefaultConfiguredProject(info), project);
1935
1936                // Verify errors
1937                verifyGetErrRequestNoErrors({ session, host, files: [main] });
1938
1939                // Verify collection of script infos
1940                service.openClientFile(dummyFilePath);
1941                verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true);
1942
1943                service.closeClientFile(main.path);
1944                service.closeClientFile(dummyFilePath);
1945                service.openClientFile(dummyFilePath);
1946                verifyProjects(/*includeConfigured*/ false, /*includeDummy*/ true);
1947
1948                service.openClientFile(main.path);
1949                service.closeClientFile(dummyFilePath);
1950                service.openClientFile(dummyFilePath);
1951                verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true);
1952
1953                // Verify Reload projects
1954                session.clearMessages();
1955                service.reloadProjects();
1956                checkEvents(session, expectedReloadEvents);
1957                verifyProjects(/*includeConfigured*/ true, /*includeDummy*/ true);
1958
1959                // Find all refs
1960                const response = session.executeCommandSeq<protocol.ReferencesRequest>({
1961                    command: protocol.CommandTypes.References,
1962                    arguments: protocolFileLocationFromSubstring(main, "foo", { index: 1 })
1963                }).response as protocol.ReferencesResponseBody;
1964                assert.deepEqual(response, expectedReferences);
1965
1966                service.closeClientFile(main.path);
1967                service.closeClientFile(dummyFilePath);
1968
1969                // Verify when declaration map references the file
1970                service.openClientFile(fileResolvingToMainDts.path);
1971                checkNumberOfProjects(service, { configuredProjects: 1 });
1972                checkProjectActualFiles(service.configuredProjects.get(tsconfigIndirect3.path)!, [tsconfigIndirect3.path, fileResolvingToMainDts.path, mainDts.path, helperDts.path, libFile.path]);
1973
1974                // Find all refs from dts include
1975                const response2 = session.executeCommandSeq<protocol.ReferencesRequest>({
1976                    command: protocol.CommandTypes.References,
1977                    arguments: protocolFileLocationFromSubstring(fileResolvingToMainDts, "foo")
1978                }).response as protocol.ReferencesResponseBody;
1979                assert.deepEqual(response2, expectedReferencesFromDtsProject);
1980
1981                function verifyProjects(includeConfigured: boolean, includeDummy: boolean) {
1982                    const inferredProjects = includeDummy ? 1 : 0;
1983                    const configuredProjects = includeConfigured ? additionalProjects.length + 2 : 0;
1984                    checkNumberOfProjects(service, { configuredProjects, inferredProjects });
1985                    if (includeConfigured) {
1986                        checkProjectActualFiles(service.configuredProjects.get(tsconfigSrc.path)!, [tsconfigSrc.path, main.path, helper.path, libFile.path]);
1987                        checkProjectActualFiles(service.configuredProjects.get(tsconfig.path)!, solutionProject || [tsconfig.path]);
1988                        additionalProjects.forEach(({ projectName, files }) =>
1989                            checkProjectActualFiles(service.configuredProjects.get(projectName)!, files));
1990                    }
1991                    if (includeDummy) {
1992                        checkProjectActualFiles(service.inferredProjects[0], [dummyFilePath, libFile.path]);
1993                    }
1994                }
1995            }
1996
1997            function expectedProjectLoadAndTelemetry(config: string, reason: string) {
1998                return [
1999                    projectLoadingStartEvent(config, reason),
2000                    projectLoadingFinishEvent(config),
2001                    projectInfoTelemetryEvent(),
2002                ];
2003            }
2004
2005            function expectedSolutionLoadAndTelemetry() {
2006                return expectedProjectLoadAndTelemetry(tsconfigPath, `Creating possible configured project for ${main.path} to open`);
2007            }
2008
2009            function expectedProjectReferenceLoadAndTelemetry(config: string) {
2010                return expectedProjectLoadAndTelemetry(config, `Creating project referenced in solution ${tsconfigPath} to find possible configured project for ${main.path} to open`);
2011            }
2012
2013            function expectedReloadEvent(config: string) {
2014                return [
2015                    projectLoadingStartEvent(config, `User requested reload projects`),
2016                    projectLoadingFinishEvent(config),
2017                    configFileDiagEvent(config, config, [])
2018                ];
2019            }
2020
2021            function expectedReferencesResponse(): protocol.ReferencesResponseBody {
2022                return {
2023                    refs: [
2024                        makeReferenceItem({
2025                            file: main,
2026                            text: "foo",
2027                            contextText: `import { foo } from 'helpers/functions';`,
2028                            isDefinition: true,
2029                            isWriteAccess: true,
2030                            lineText: `import { foo } from 'helpers/functions';`,
2031                        }),
2032                        makeReferenceItem({
2033                            file: main,
2034                            text: "foo",
2035                            options: { index: 1 },
2036                            contextText: `export { foo };`,
2037                            isDefinition: true,
2038                            isWriteAccess: true,
2039                            lineText: `export { foo };`,
2040                        }),
2041                        makeReferenceItem({
2042                            file: helper,
2043                            text: "foo",
2044                            contextText: `export const foo = 1;`,
2045                            isDefinition: true,
2046                            isWriteAccess: true,
2047                            lineText: `export const foo = 1;`,
2048                        }),
2049                    ],
2050                    symbolName: "foo",
2051                    symbolStartOffset: protocolLocationFromSubstring(main.content, "foo").offset,
2052                    symbolDisplayString: "(alias) const foo: 1\nexport foo"
2053                };
2054            }
2055
2056            function expectedIndirectRefs(indirect: File) {
2057                return [
2058                    makeReferenceItem({
2059                        file: indirect,
2060                        text: "foo",
2061                        contextText: `import { foo } from 'main';`,
2062                        isDefinition: true,
2063                        isWriteAccess: true,
2064                        lineText: `import { foo } from 'main';`,
2065                    }),
2066                    makeReferenceItem({
2067                        file: indirect,
2068                        text: "foo",
2069                        options: { index: 1 },
2070                        isDefinition: false,
2071                        isWriteAccess: false,
2072                        lineText: `foo;`,
2073                    }),
2074                ];
2075            }
2076
2077            function getIndirectProject(postfix: string, optionsToExtend?: CompilerOptions) {
2078                const tsconfigIndirect: File = {
2079                    path: `${tscWatch.projectRoot}/tsconfig-indirect${postfix}.json`,
2080                    content: JSON.stringify({
2081                        compilerOptions: {
2082                            composite: true,
2083                            outDir: "./target/",
2084                            baseUrl: "./src/",
2085                            ...optionsToExtend
2086                        },
2087                        files: [`./indirect${postfix}/main.ts`],
2088                        references: [{ path: "./tsconfig-src.json" }]
2089                    })
2090                };
2091                const indirect: File = {
2092                    path: `${tscWatch.projectRoot}/indirect${postfix}/main.ts`,
2093                    content: fileResolvingToMainDts.content
2094                };
2095                return { tsconfigIndirect, indirect };
2096            }
2097
2098            interface VerifyProjects {
2099                configuredProjects: readonly { projectName: string, files: readonly string[] }[];
2100                inferredProjects: readonly (readonly string[])[];
2101            }
2102
2103            interface VerifyDisableReferencedProjectLoad extends Setup {
2104                expectedProjectsOnOpen: VerifyProjects;
2105                expectedProjectsOnDummyOpen?: VerifyProjects;
2106                expectedProjectsOnReload?: VerifyProjects;
2107                expectedDefaultProject: (service: server.ProjectService) => server.Project;
2108                expectedDefaultConfiguredProject: (service: server.ProjectService) => server.ConfiguredProject | undefined;
2109                expectedReloadEvents: protocol.Event[];
2110            }
2111
2112            function verifyDisableReferencedProjectLoad(input: VerifyDisableReferencedProjectLoad) {
2113                const { session, service } = setup(input);
2114                const { expectedProjectsOnOpen, expectedDefaultProject, expectedDefaultConfiguredProject, expectedReloadEvents } = input;
2115                const expectedProjectsOnOnlyDummy: VerifyProjects = {
2116                    configuredProjects: emptyArray,
2117                    inferredProjects: [
2118                        [dummyFilePath, libFile.path],
2119                    ]
2120                };
2121                const expectedProjectsOnDummyOpen = input.expectedProjectsOnDummyOpen || {
2122                    configuredProjects: expectedProjectsOnOpen.configuredProjects,
2123                    inferredProjects: expectedProjectsOnOnlyDummy.inferredProjects,
2124                };
2125                const expectedProjectsOnReload = input.expectedProjectsOnReload || expectedProjectsOnDummyOpen;
2126
2127                verifyProjects(expectedProjectsOnOpen);
2128                const info = service.getScriptInfoForPath(main.path as Path)!;
2129                assert.equal(info.getDefaultProject(), expectedDefaultProject(service));
2130                assert.equal(service.findDefaultConfiguredProject(info), expectedDefaultConfiguredProject(service));
2131
2132                // Verify collection of script infos
2133                service.openClientFile(dummyFilePath);
2134                verifyProjects(expectedProjectsOnDummyOpen);
2135
2136                service.closeClientFile(main.path);
2137                service.closeClientFile(dummyFilePath);
2138                service.openClientFile(dummyFilePath);
2139                verifyProjects(expectedProjectsOnOnlyDummy);
2140
2141                service.openClientFile(main.path);
2142
2143                // Verify Reload projects
2144                session.clearMessages();
2145                service.reloadProjects();
2146                checkEvents(session, expectedReloadEvents);
2147                verifyProjects(expectedProjectsOnReload);
2148
2149                function verifyProjects(expected: VerifyProjects) {
2150                    checkNumberOfProjects(service, { configuredProjects: expected.configuredProjects.length, inferredProjects: expected.inferredProjects.length });
2151                    expected.configuredProjects.forEach(({ projectName, files }) =>
2152                        checkProjectActualFiles(service.configuredProjects.get(projectName)!, files));
2153                    expected.inferredProjects.forEach((files, index) =>
2154                        checkProjectActualFiles(service.inferredProjects[index], files));
2155                }
2156            }
2157
2158            it("when project is directly referenced by solution", () => {
2159                const expectedReferences = expectedReferencesResponse();
2160                verifySolutionScenario({
2161                    configRefs: ["./tsconfig-src.json"],
2162                    additionalFiles: emptyArray,
2163                    additionalProjects: emptyArray,
2164                    expectedOpenEvents: [
2165                        ...expectedSolutionLoadAndTelemetry(),
2166                        ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
2167                        configFileDiagEvent(main.path, tsconfigSrcPath, [])
2168                    ],
2169                    expectedReloadEvents: [
2170                        ...expectedReloadEvent(tsconfigPath),
2171                        ...expectedReloadEvent(tsconfigSrcPath),
2172                    ],
2173                    expectedReferences,
2174                    expectedReferencesFromDtsProject: {
2175                        ...expectedReferences,
2176                        refs: [
2177                            ...expectedIndirectRefs(fileResolvingToMainDts),
2178                            ...expectedReferences.refs
2179                        ],
2180                        symbolDisplayString: "(alias) const foo: 1\nimport foo",
2181                    }
2182                });
2183            });
2184
2185            it("when project is indirectly referenced by solution", () => {
2186                const { tsconfigIndirect, indirect } = getIndirectProject("1");
2187                const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
2188                const { refs, ...rest } = expectedReferencesResponse();
2189                verifySolutionScenario({
2190                    configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
2191                    additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2],
2192                    additionalProjects: emptyArray,
2193                    expectedOpenEvents: [
2194                        ...expectedSolutionLoadAndTelemetry(),
2195                        ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
2196                        configFileDiagEvent(main.path, tsconfigSrcPath, [])
2197                    ],
2198                    expectedReloadEvents: [
2199                        ...expectedReloadEvent(tsconfigPath),
2200                        ...expectedReloadEvent(tsconfigSrcPath),
2201                    ],
2202                    expectedReferences: {
2203                        refs: [
2204                            ...refs,
2205                            ...expectedIndirectRefs(indirect),
2206                            ...expectedIndirectRefs(indirect2),
2207                        ],
2208                        ...rest
2209                    },
2210                    expectedReferencesFromDtsProject: {
2211                        ...rest,
2212                        refs: [
2213                            ...expectedIndirectRefs(fileResolvingToMainDts),
2214                            ...refs,
2215                            ...expectedIndirectRefs(indirect),
2216                            ...expectedIndirectRefs(indirect2),
2217                        ],
2218                        symbolDisplayString: "(alias) const foo: 1\nimport foo",
2219                    }
2220                });
2221            });
2222
2223            it("disables looking into the child project if disableReferencedProjectLoad is set", () => {
2224                const expectedProjectsOnOpen: VerifyProjects = {
2225                    configuredProjects: [
2226                        { projectName: tsconfigPath, files: [tsconfigPath] },
2227                    ],
2228                    inferredProjects: [
2229                        [main.path, libFile.path],
2230                    ]
2231                };
2232                verifyDisableReferencedProjectLoad({
2233                    solutionOptions: { disableReferencedProjectLoad: true },
2234                    configRefs: ["./tsconfig-src.json"],
2235                    additionalFiles: emptyArray,
2236                    expectedOpenEvents: [
2237                        ...expectedSolutionLoadAndTelemetry(),
2238                        configFileDiagEvent(main.path, tsconfigPath, [])
2239                    ],
2240                    expectedDefaultProject: service => service.inferredProjects[0],
2241                    expectedDefaultConfiguredProject: returnUndefined,
2242                    expectedProjectsOnOpen,
2243                    expectedProjectsOnDummyOpen: {
2244                        configuredProjects: emptyArray,
2245                        inferredProjects: [
2246                            ...expectedProjectsOnOpen.inferredProjects,
2247                            [dummyFilePath, libFile.path],
2248                        ]
2249                    },
2250                    expectedProjectsOnReload: {
2251                        configuredProjects: expectedProjectsOnOpen.configuredProjects,
2252                        inferredProjects: [
2253                            [dummyFilePath, libFile.path],
2254                            ...expectedProjectsOnOpen.inferredProjects,
2255                        ]
2256                    },
2257                    expectedReloadEvents: expectedReloadEvent(tsconfigPath)
2258                });
2259            });
2260
2261            it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => {
2262                const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
2263                const expectedProjectsOnOpen: VerifyProjects = {
2264                    configuredProjects: [
2265                        { projectName: tsconfigPath, files: [tsconfigPath] },
2266                        { projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
2267                    ],
2268                    inferredProjects: emptyArray
2269                };
2270                verifyDisableReferencedProjectLoad({
2271                    configRefs: ["./tsconfig-indirect1.json"],
2272                    additionalFiles: [tsconfigIndirect, indirect],
2273                    expectedOpenEvents: [
2274                        ...expectedSolutionLoadAndTelemetry(),
2275                        ...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
2276                        configFileDiagEvent(main.path, tsconfigIndirect.path, [])
2277                    ],
2278                    expectedDefaultProject: service => service.configuredProjects.get(tsconfigIndirect.path)!,
2279                    expectedDefaultConfiguredProject: returnUndefined,
2280                    expectedProjectsOnOpen,
2281                    expectedReloadEvents: [
2282                        ...expectedReloadEvent(tsconfigPath),
2283                        ...expectedReloadEvent(tsconfigIndirect.path),
2284                    ]
2285                });
2286            });
2287
2288            it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => {
2289                const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
2290                const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
2291                const expectedProjectsOnOpen: VerifyProjects = {
2292                    configuredProjects: [
2293                        { projectName: tsconfigPath, files: [tsconfigPath] },
2294                        { projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] },
2295                    ],
2296                    inferredProjects: emptyArray
2297                };
2298                verifyDisableReferencedProjectLoad({
2299                    configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
2300                    additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2],
2301                    expectedOpenEvents: [
2302                        ...expectedSolutionLoadAndTelemetry(),
2303                        ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
2304                        configFileDiagEvent(main.path, tsconfigSrcPath, [])
2305                    ],
2306                    expectedDefaultProject: service => service.configuredProjects.get(tsconfigSrcPath)!,
2307                    expectedDefaultConfiguredProject: service => service.configuredProjects.get(tsconfigSrcPath)!,
2308                    expectedProjectsOnOpen,
2309                    expectedReloadEvents: [
2310                        ...expectedReloadEvent(tsconfigPath),
2311                        ...expectedReloadEvent(tsconfigSrcPath),
2312                    ]
2313                });
2314            });
2315
2316            describe("when solution is project that contains its own files", () => {
2317                it("when the project found is not solution but references open file through project reference", () => {
2318                    const ownMain: File = {
2319                        path: `${tscWatch.projectRoot}/own/main.ts`,
2320                        content: fileResolvingToMainDts.content
2321                    };
2322                    const { refs, ...rest } = expectedReferencesResponse();
2323                    verifySolutionScenario({
2324                        solutionFiles: [`./own/main.ts`],
2325                        solutionOptions: {
2326                            outDir: "./target/",
2327                            baseUrl: "./src/"
2328                        },
2329                        solutionProject: [tsconfigPath, ownMain.path, main.path, libFile.path, helper.path],
2330                        configRefs: ["./tsconfig-src.json"],
2331                        additionalFiles: [ownMain],
2332                        additionalProjects: emptyArray,
2333                        expectedOpenEvents: [
2334                            ...expectedSolutionLoadAndTelemetry(),
2335                            ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
2336                            configFileDiagEvent(main.path, tsconfigSrcPath, [])
2337                        ],
2338                        expectedReloadEvents: [
2339                            ...expectedReloadEvent(tsconfigPath),
2340                            ...expectedReloadEvent(tsconfigSrcPath),
2341                        ],
2342                        expectedReferences: {
2343                            refs: [
2344                                ...refs,
2345                                ...expectedIndirectRefs(ownMain),
2346                            ],
2347                            ...rest
2348                        },
2349                        expectedReferencesFromDtsProject: {
2350                            ...rest,
2351                            refs: [
2352                                ...expectedIndirectRefs(fileResolvingToMainDts),
2353                                ...refs,
2354                                ...expectedIndirectRefs(ownMain),
2355                            ],
2356                            symbolDisplayString: "(alias) const foo: 1\nimport foo",
2357                        },
2358                    });
2359                });
2360
2361                it("when project is indirectly referenced by solution", () => {
2362                    const ownMain: File = {
2363                        path: `${tscWatch.projectRoot}/own/main.ts`,
2364                        content: `import { bar } from 'main';
2365bar;`
2366                    };
2367                    const { tsconfigIndirect, indirect } = getIndirectProject("1");
2368                    const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
2369                    const { refs, ...rest } = expectedReferencesResponse();
2370                    verifySolutionScenario({
2371                        solutionFiles: [`./own/main.ts`],
2372                        solutionOptions: {
2373                            outDir: "./target/",
2374                            baseUrl: "./indirect1/"
2375                        },
2376                        solutionProject: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path],
2377                        configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
2378                        additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain],
2379                        additionalProjects: emptyArray,
2380                        expectedOpenEvents: [
2381                            ...expectedSolutionLoadAndTelemetry(),
2382                            ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
2383                            configFileDiagEvent(main.path, tsconfigSrcPath, [])
2384                        ],
2385                        expectedReloadEvents: [
2386                            ...expectedReloadEvent(tsconfigPath),
2387                            ...expectedReloadEvent(tsconfigSrcPath),
2388                        ],
2389                        expectedReferences: {
2390                            refs: [
2391                                ...refs,
2392                                ...expectedIndirectRefs(indirect),
2393                                ...expectedIndirectRefs(indirect2),
2394                            ],
2395                            ...rest
2396                        },
2397                        expectedReferencesFromDtsProject: {
2398                            ...rest,
2399                            refs: [
2400                                ...expectedIndirectRefs(fileResolvingToMainDts),
2401                                ...refs,
2402                                ...expectedIndirectRefs(indirect),
2403                                ...expectedIndirectRefs(indirect2),
2404                            ],
2405                            symbolDisplayString: "(alias) const foo: 1\nimport foo",
2406                        }
2407                    });
2408                });
2409
2410                it("disables looking into the child project if disableReferencedProjectLoad is set", () => {
2411                    const ownMain: File = {
2412                        path: `${tscWatch.projectRoot}/own/main.ts`,
2413                        content: fileResolvingToMainDts.content
2414                    };
2415                    const expectedProjectsOnOpen: VerifyProjects = {
2416                        configuredProjects: [
2417                            { projectName: tsconfigPath, files: [tsconfigPath, ownMain.path, main.path, libFile.path, helper.path] },
2418                        ],
2419                        inferredProjects: emptyArray
2420                    };
2421                    verifyDisableReferencedProjectLoad({
2422                        solutionFiles: [`./own/main.ts`],
2423                        solutionOptions: {
2424                            outDir: "./target/",
2425                            baseUrl: "./src/",
2426                            disableReferencedProjectLoad: true
2427                        },
2428                        configRefs: ["./tsconfig-src.json"],
2429                        additionalFiles: [ownMain],
2430                        expectedOpenEvents: [
2431                            ...expectedSolutionLoadAndTelemetry(),
2432                            configFileDiagEvent(main.path, tsconfigPath, [])
2433                        ],
2434                        expectedDefaultProject: service => service.configuredProjects.get(tsconfigPath)!,
2435                        expectedDefaultConfiguredProject: returnUndefined,
2436                        expectedProjectsOnOpen,
2437                        expectedReloadEvents: expectedReloadEvent(tsconfigPath)
2438                    });
2439                });
2440
2441                it("disables looking into the child project if disableReferencedProjectLoad is set in indirect project", () => {
2442                    const ownMain: File = {
2443                        path: `${tscWatch.projectRoot}/own/main.ts`,
2444                        content: `import { bar } from 'main';
2445bar;`
2446                    };
2447                    const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
2448                    const expectedProjectsOnOpen: VerifyProjects = {
2449                        configuredProjects: [
2450                            { projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] },
2451                            { projectName: tsconfigIndirect.path, files: [tsconfigIndirect.path, main.path, helper.path, indirect.path, libFile.path] },
2452                        ],
2453                        inferredProjects: emptyArray
2454                    };
2455                    verifyDisableReferencedProjectLoad({
2456                        solutionFiles: [`./own/main.ts`],
2457                        solutionOptions: {
2458                            outDir: "./target/",
2459                            baseUrl: "./indirect1/",
2460                        },
2461                        configRefs: ["./tsconfig-indirect1.json"],
2462                        additionalFiles: [tsconfigIndirect, indirect, ownMain],
2463                        expectedOpenEvents: [
2464                            ...expectedSolutionLoadAndTelemetry(),
2465                            ...expectedProjectReferenceLoadAndTelemetry(tsconfigIndirect.path),
2466                            configFileDiagEvent(main.path, tsconfigPath, [])
2467                        ],
2468                        expectedDefaultProject: service => service.configuredProjects.get(tsconfigPath)!,
2469                        expectedDefaultConfiguredProject: returnUndefined,
2470                        expectedProjectsOnOpen,
2471                        expectedReloadEvents: [
2472                            ...expectedReloadEvent(tsconfigPath),
2473                            ...expectedReloadEvent(tsconfigIndirect.path),
2474                        ]
2475                    });
2476                });
2477
2478                it("disables looking into the child project if disableReferencedProjectLoad is set in first indirect project but not in another one", () => {
2479                    const ownMain: File = {
2480                        path: `${tscWatch.projectRoot}/own/main.ts`,
2481                        content: `import { bar } from 'main';
2482bar;`
2483                    };
2484                    const { tsconfigIndirect, indirect } = getIndirectProject("1", { disableReferencedProjectLoad: true });
2485                    const { tsconfigIndirect: tsconfigIndirect2, indirect: indirect2 } = getIndirectProject("2");
2486                    const expectedProjectsOnOpen: VerifyProjects = {
2487                        configuredProjects: [
2488                            { projectName: tsconfigPath, files: [tsconfigPath, indirect.path, ownMain.path, main.path, libFile.path, helper.path] },
2489                            { projectName: tsconfigSrcPath, files: [tsconfigSrcPath, main.path, helper.path, libFile.path] },
2490                        ],
2491                        inferredProjects: emptyArray
2492                    };
2493                    verifyDisableReferencedProjectLoad({
2494                        solutionFiles: [`./own/main.ts`],
2495                        solutionOptions: {
2496                            outDir: "./target/",
2497                            baseUrl: "./indirect1/",
2498                        },
2499                        configRefs: ["./tsconfig-indirect1.json", "./tsconfig-indirect2.json"],
2500                        additionalFiles: [tsconfigIndirect, indirect, tsconfigIndirect2, indirect2, ownMain],
2501                        expectedOpenEvents: [
2502                            ...expectedSolutionLoadAndTelemetry(),
2503                            ...expectedProjectReferenceLoadAndTelemetry(tsconfigSrcPath),
2504                            configFileDiagEvent(main.path, tsconfigSrcPath, [])
2505                        ],
2506                        expectedDefaultProject: service => service.configuredProjects.get(tsconfigSrcPath)!,
2507                        expectedDefaultConfiguredProject: service => service.configuredProjects.get(tsconfigSrcPath)!,
2508                        expectedProjectsOnOpen,
2509                        expectedReloadEvents: [
2510                            ...expectedReloadEvent(tsconfigPath),
2511                            ...expectedReloadEvent(tsconfigSrcPath),
2512                        ]
2513                    });
2514                });
2515            });
2516        });
2517
2518        describe("auto import with referenced project", () => {
2519            function verifyAutoImport(built: boolean, disableSourceOfProjectReferenceRedirect?: boolean) {
2520                const solnConfig: File = {
2521                    path: `${tscWatch.projectRoot}/tsconfig.json`,
2522                    content: JSON.stringify({
2523                        files: [],
2524                        references: [
2525                            { path: "shared/src/library" },
2526                            { path: "app/src/program" }
2527                        ]
2528                    })
2529                };
2530                const sharedConfig: File = {
2531                    path: `${tscWatch.projectRoot}/shared/src/library/tsconfig.json`,
2532                    content: JSON.stringify({
2533                        compilerOptions: {
2534                            composite: true,
2535                            outDir: "../../bld/library"
2536                        }
2537                    })
2538                };
2539                const sharedIndex: File = {
2540                    path: `${tscWatch.projectRoot}/shared/src/library/index.ts`,
2541                    content: `export function foo() {}`
2542                };
2543                const sharedPackage: File = {
2544                    path: `${tscWatch.projectRoot}/shared/package.json`,
2545                    content: JSON.stringify({
2546                        name: "shared",
2547                        version: "1.0.0",
2548                        main: "bld/library/index.js",
2549                        types: "bld/library/index.d.ts"
2550                    })
2551                };
2552                const appConfig: File = {
2553                    path: `${tscWatch.projectRoot}/app/src/program/tsconfig.json`,
2554                    content: JSON.stringify({
2555                        compilerOptions: {
2556                            composite: true,
2557                            outDir: "../../bld/program",
2558                            disableSourceOfProjectReferenceRedirect
2559                        },
2560                        references: [
2561                            { path: "../../../shared/src/library" }
2562                        ]
2563                    })
2564                };
2565                const appBar: File = {
2566                    path: `${tscWatch.projectRoot}/app/src/program/bar.ts`,
2567                    content: `import {foo} from "shared";`
2568                };
2569                const appIndex: File = {
2570                    path: `${tscWatch.projectRoot}/app/src/program/index.ts`,
2571                    content: `foo`
2572                };
2573                const sharedSymlink: SymLink = {
2574                    path: `${tscWatch.projectRoot}/node_modules/shared`,
2575                    symLink: `${tscWatch.projectRoot}/shared`
2576                };
2577                const files = [solnConfig, sharedConfig, sharedIndex, sharedPackage, appConfig, appBar, appIndex, sharedSymlink, libFile];
2578                const host = createServerHost(files);
2579                if (built) {
2580                    const solutionBuilder = tscWatch.createSolutionBuilder(host, [solnConfig.path], {});
2581                    solutionBuilder.build();
2582                    host.clearOutput();
2583                }
2584                const session = createSession(host);
2585                openFilesForSession([appIndex], session);
2586                const response = session.executeCommandSeq<protocol.CodeFixRequest>({
2587                    command: protocol.CommandTypes.GetCodeFixes,
2588                    arguments: {
2589                        file: appIndex.path,
2590                        startLine: 1,
2591                        startOffset: 1,
2592                        endLine: 1,
2593                        endOffset: 4,
2594                        errorCodes: [Diagnostics.Cannot_find_name_0.code],
2595                    }
2596                }).response as protocol.CodeFixAction[];
2597                assert.deepEqual(response, [
2598                    {
2599                        fixName: "import",
2600                        description: `Import 'foo' from module "shared"`,
2601                        changes: [{
2602                            fileName: appIndex.path,
2603                            textChanges: [{
2604                                start: { line: 1, offset: 1 },
2605                                end: { line: 1, offset: 1 },
2606                                newText: 'import { foo } from "shared";\n\n',
2607                            }],
2608                        }],
2609                        commands: undefined,
2610                        fixAllDescription: undefined,
2611                        fixId: undefined
2612                    }
2613                ]);
2614            }
2615
2616            it("when project is built", () => {
2617                verifyAutoImport(/*built*/ true);
2618            });
2619            it("when project is not built", () => {
2620                verifyAutoImport(/*built*/ false);
2621            });
2622            it("when disableSourceOfProjectReferenceRedirect is true", () => {
2623                verifyAutoImport(/*built*/ true, /*disableSourceOfProjectReferenceRedirect*/ true);
2624            });
2625        });
2626
2627        it("when files from two projects are open and one project references", () => {
2628            function getPackageAndFile(packageName: string, references?: string[], optionsToExtend?: CompilerOptions): [file: File, config: File] {
2629                const file: File = {
2630                    path: `${tscWatch.projectRoot}/${packageName}/src/file1.ts`,
2631                    content: `export const ${packageName}Const = 10;`
2632                };
2633                const config: File = {
2634                    path: `${tscWatch.projectRoot}/${packageName}/tsconfig.json`,
2635                    content: JSON.stringify({
2636                        compilerOptions: { composite: true, ...optionsToExtend || {} },
2637                        references: references?.map(path => ({ path: `../${path}` }))
2638                    })
2639                };
2640                return [file, config];
2641            }
2642            const [mainFile, mainConfig] = getPackageAndFile("main", ["core", "indirect", "noCoreRef1", "indirectDisabledChildLoad1", "indirectDisabledChildLoad2", "refToCoreRef3", "indirectNoCoreRef"]);
2643            const [coreFile, coreConfig] = getPackageAndFile("core");
2644            const [noCoreRef1File, noCoreRef1Config] = getPackageAndFile("noCoreRef1");
2645            const [indirectFile, indirectConfig] = getPackageAndFile("indirect", ["coreRef1"]);
2646            const [coreRef1File, coreRef1Config] = getPackageAndFile("coreRef1", ["core"]);
2647            const [indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config] = getPackageAndFile("indirectDisabledChildLoad1", ["coreRef2"], { disableReferencedProjectLoad: true });
2648            const [coreRef2File, coreRef2Config] = getPackageAndFile("coreRef2", ["core"]);
2649            const [indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config] = getPackageAndFile("indirectDisabledChildLoad2", ["coreRef3"], { disableReferencedProjectLoad: true });
2650            const [coreRef3File, coreRef3Config] = getPackageAndFile("coreRef3", ["core"]);
2651            const [refToCoreRef3File, refToCoreRef3Config] = getPackageAndFile("refToCoreRef3", ["coreRef3"]);
2652            const [indirectNoCoreRefFile, indirectNoCoreRefConfig] = getPackageAndFile("indirectNoCoreRef", ["noCoreRef2"]);
2653            const [noCoreRef2File, noCoreRef2Config] = getPackageAndFile("noCoreRef2");
2654
2655            const host = createServerHost([
2656                libFile, mainFile, mainConfig, coreFile, coreConfig, noCoreRef1File, noCoreRef1Config,
2657                indirectFile, indirectConfig, coreRef1File, coreRef1Config,
2658                indirectDisabledChildLoad1File, indirectDisabledChildLoad1Config, coreRef2File, coreRef2Config,
2659                indirectDisabledChildLoad2File, indirectDisabledChildLoad2Config, coreRef3File, coreRef3Config,
2660                refToCoreRef3File, refToCoreRef3Config,
2661                indirectNoCoreRefFile, indirectNoCoreRefConfig, noCoreRef2File, noCoreRef2Config
2662            ], { useCaseSensitiveFileNames: true });
2663            const session = createSession(host);
2664            const service = session.getProjectService();
2665            openFilesForSession([mainFile, coreFile], session);
2666
2667            verifyProject(mainConfig);
2668            verifyProject(coreConfig);
2669
2670            // Find all refs in coreFile
2671            session.executeCommandSeq<protocol.ReferencesRequest>({
2672                command: protocol.CommandTypes.References,
2673                arguments: protocolFileLocationFromSubstring(coreFile, `coreConst`)
2674            });
2675            verifyProject(mainConfig);
2676            verifyProject(coreConfig);
2677            verifyNoProject(noCoreRef1Config); // Should not be loaded
2678            verifyProject(indirectConfig);
2679            verifyProject(coreRef1Config);
2680            verifyProject(indirectDisabledChildLoad1Config);
2681            verifyNoProject(coreRef2Config); // Should not be loaded
2682            verifyProject(indirectDisabledChildLoad2Config);
2683            verifyProject(coreRef3Config);
2684            verifyProject(refToCoreRef3Config);
2685            verifyNoProject(indirectNoCoreRefConfig); // Should not be loaded
2686            verifyNoProject(noCoreRef2Config); // Should not be loaded
2687
2688            function verifyProject(config: File) {
2689                assert.isDefined(service.configuredProjects.get(config.path), `Expected to find ${config.path}`);
2690            }
2691
2692            function verifyNoProject(config: File) {
2693                assert.isUndefined(service.configuredProjects.get(config.path), `Expected to not find ${config.path}`);
2694            }
2695        });
2696    });
2697}
2698