• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: symLinks", () => {
3        it("rename in common file renames all project", () => {
4            const projects = "/users/username/projects";
5            const folderA = `${projects}/a`;
6            const aFile: File = {
7                path: `${folderA}/a.ts`,
8                content: `import {C} from "./c/fc"; console.log(C)`
9            };
10            const aTsconfig: File = {
11                path: `${folderA}/tsconfig.json`,
12                content: JSON.stringify({ compilerOptions: { module: "commonjs" } })
13            };
14            const aC: SymLink = {
15                path: `${folderA}/c`,
16                symLink: "../c"
17            };
18            const aFc = `${folderA}/c/fc.ts`;
19
20            const folderB = `${projects}/b`;
21            const bFile: File = {
22                path: `${folderB}/b.ts`,
23                content: `import {C} from "./c/fc"; console.log(C)`
24            };
25            const bTsconfig: File = {
26                path: `${folderB}/tsconfig.json`,
27                content: JSON.stringify({ compilerOptions: { module: "commonjs" } })
28            };
29            const bC: SymLink = {
30                path: `${folderB}/c`,
31                symLink: "../c"
32            };
33            const bFc = `${folderB}/c/fc.ts`;
34
35            const folderC = `${projects}/c`;
36            const cFile: File = {
37                path: `${folderC}/fc.ts`,
38                content: `export const C = 8`
39            };
40
41            const files = [cFile, libFile, aFile, aTsconfig, aC, bFile, bTsconfig, bC];
42            const host = createServerHost(files);
43            const session = createSession(host);
44            const projectService = session.getProjectService();
45            openFilesForSession(
46                [
47                    { file: aFile, projectRootPath: folderA },
48                    { file: bFile, projectRootPath: folderB },
49                    { file: aFc, projectRootPath: folderA },
50                    { file: bFc, projectRootPath: folderB },
51                ],
52                session);
53            checkNumberOfProjects(projectService, { configuredProjects: 2 });
54            assert.isDefined(projectService.configuredProjects.get(aTsconfig.path));
55            assert.isDefined(projectService.configuredProjects.get(bTsconfig.path));
56
57            const response = executeSessionRequest<protocol.RenameRequest, protocol.RenameResponse>(session, protocol.CommandTypes.Rename, { file: aFc, ...protocolLocationFromSubstring(cFile.content, "C") });
58
59            assert.equal(aFile.content, bFile.content);
60            const abLocs: protocol.RenameTextSpan[] = [
61                protocolRenameSpanFromSubstring({
62                    fileText: aFile.content,
63                    text: "C",
64                    contextText: `import {C} from "./c/fc";`
65                }),
66                protocolRenameSpanFromSubstring({
67                    fileText: aFile.content,
68                    text: "C",
69                    options: { index: 1 }
70                }),
71            ];
72            const span = protocolRenameSpanFromSubstring({
73                fileText: cFile.content,
74                text: "C",
75                contextText: "export const C = 8"
76            });
77            const cLocs: protocol.RenameTextSpan[] = [span];
78            assert.deepEqual<protocol.RenameResponseBody | undefined>(response, {
79                info: {
80                    canRename: true,
81                    displayName: "C",
82                    fileToRename: undefined,
83                    fullDisplayName: '"/users/username/projects/a/c/fc".C',
84                    kind: ScriptElementKind.constElement,
85                    kindModifiers: ScriptElementKindModifier.exportedModifier,
86                    triggerSpan: protocolTextSpanFromSubstring(cFile.content, "C"),
87                },
88                locs: [
89                    { file: aFc, locs: cLocs },
90                    { file: aFile.path, locs: abLocs },
91                    { file: bFc, locs: cLocs },
92                    { file: bFile.path, locs: abLocs },
93                ],
94            });
95        });
96
97        describe("module resolution when symlinked folder contents change and resolve modules", () => {
98            const projectRootPath = "/users/username/projects/myproject";
99            const packages = `${projectRootPath}/javascript/packages`;
100            const recognizersDateTime = `${packages}/recognizers-date-time`;
101            const recognizersText = `${packages}/recognizers-text`;
102            const recognizersTextDist = `${recognizersText}/dist`;
103            const moduleName = "@microsoft/recognizers-text";
104            const moduleNameInFile = `"${moduleName}"`;
105            const recognizersDateTimeSrcFile: File = {
106                path: `${recognizersDateTime}/src/datetime/baseDate.ts`,
107                content: `import {C} from ${moduleNameInFile};
108new C();`
109            };
110            const recognizerDateTimeTsconfigPath = `${recognizersDateTime}/tsconfig.json`;
111            const recognizerDateTimeTsconfigWithoutPathMapping: File = {
112                path: recognizerDateTimeTsconfigPath,
113                content: JSON.stringify({
114                    include: ["src"]
115                })
116            };
117            const recognizerDateTimeTsconfigWithPathMapping: File = {
118                path: recognizerDateTimeTsconfigPath,
119                content: JSON.stringify({
120                    compilerOptions: {
121                        rootDir: "src",
122                        baseUrl: "./",
123                        paths: {
124                            "@microsoft/*": ["../*"]
125                        }
126                    },
127                    include: ["src"]
128                })
129            };
130            const nodeModulesRecorgnizersText: SymLink = {
131                path: `${recognizersDateTime}/node_modules/@microsoft/recognizers-text`,
132                symLink: recognizersText
133            };
134            const recognizerTextSrcFile: File = {
135                path: `${recognizersText}/src/recognizers-text.ts`,
136                content: `export class C { method () { return 10; } }`
137            };
138            const recongnizerTextDistTypingFile: File = {
139                path: `${recognizersTextDist}/types/recognizers-text.d.ts`,
140                content: `export class C { method(): number; }`
141            };
142            const recongnizerTextPackageJson: File = {
143                path: `${recognizersText}/package.json`,
144                content: JSON.stringify({
145                    typings: "dist/types/recognizers-text.d.ts"
146                })
147            };
148            const filesInProjectWithUnresolvedModule = [recognizerDateTimeTsconfigPath, libFile.path, recognizersDateTimeSrcFile.path];
149            const filesInProjectWithResolvedModule = [...filesInProjectWithUnresolvedModule, recongnizerTextDistTypingFile.path];
150
151            function verifyErrors(session: TestSession, semanticErrors: protocol.Diagnostic[]) {
152                verifyGetErrRequest({
153                    session,
154                    host: session.testhost,
155                    expected: [{
156                        file: recognizersDateTimeSrcFile,
157                        syntax: [],
158                        semantic: semanticErrors,
159                        suggestion: []
160                    }]
161                });
162            }
163
164            function verifyWatchedFilesAndDirectories(host: TestServerHost, files: string[], recursiveDirectories: ReadonlyESMap<string, number>, nonRecursiveDirectories: string[]) {
165                checkWatchedFilesDetailed(host, files.filter(f => f !== recognizersDateTimeSrcFile.path), 1);
166                checkWatchedDirectoriesDetailed(host, nonRecursiveDirectories, 1, /*recursive*/ false);
167                checkWatchedDirectoriesDetailed(host, recursiveDirectories, /*recursive*/ true);
168            }
169
170            function createSessionAndOpenFile(host: TestServerHost) {
171                const session = createSession(host, { canUseEvents: true });
172                session.executeCommandSeq<protocol.OpenRequest>({
173                    command: protocol.CommandTypes.Open,
174                    arguments: {
175                        file: recognizersDateTimeSrcFile.path,
176                        projectRootPath
177                    }
178                });
179                return session;
180            }
181
182            function verifyModuleResolution(withPathMapping: boolean) {
183                describe(withPathMapping ? "when tsconfig file contains path mapping" : "when tsconfig does not contain path mapping", () => {
184                    const filesWithSources = [libFile, recognizersDateTimeSrcFile, withPathMapping ? recognizerDateTimeTsconfigWithPathMapping : recognizerDateTimeTsconfigWithoutPathMapping, recognizerTextSrcFile, recongnizerTextPackageJson];
185
186                    const watchedDirectoriesWithResolvedModule = arrayToMap(getTypeRootsFromLocation(recognizersDateTime), k => k, () => 1);
187                    watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/src`, withPathMapping ? 1 : 2); // wild card + failed lookups
188                    if (!withPathMapping) {
189                        watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/node_modules`, 1); // failed lookups
190                    }
191                    const watchedDirectoriesWithUnresolvedModule = new Map(watchedDirectoriesWithResolvedModule);
192                    watchedDirectoriesWithUnresolvedModule.set(`${recognizersDateTime}/src`, 2); // wild card + failed lookups
193                    [`${recognizersDateTime}/node_modules`, ...(withPathMapping ? [recognizersText] : emptyArray), ...getNodeModuleDirectories(packages)].forEach(d => {
194                        watchedDirectoriesWithUnresolvedModule.set(d, 1);
195                    });
196                    const nonRecursiveWatchedDirectories = withPathMapping ? [packages] : emptyArray;
197
198                    function verifyProjectWithResolvedModule(session: TestSession) {
199                        const projectService = session.getProjectService();
200                        const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath)!;
201                        checkProjectActualFiles(project, filesInProjectWithResolvedModule);
202                        verifyWatchedFilesAndDirectories(session.testhost, filesInProjectWithResolvedModule, watchedDirectoriesWithResolvedModule, nonRecursiveWatchedDirectories);
203                        verifyErrors(session, []);
204                    }
205
206                    function verifyProjectWithUnresolvedModule(session: TestSession) {
207                        const projectService = session.getProjectService();
208                        const project = projectService.configuredProjects.get(recognizerDateTimeTsconfigPath)!;
209                        checkProjectActualFiles(project, filesInProjectWithUnresolvedModule);
210                        verifyWatchedFilesAndDirectories(session.testhost, filesInProjectWithUnresolvedModule, watchedDirectoriesWithUnresolvedModule, nonRecursiveWatchedDirectories);
211                        const startOffset = recognizersDateTimeSrcFile.content.indexOf('"') + 1;
212                        verifyErrors(session, [
213                            createDiagnostic({ line: 1, offset: startOffset }, { line: 1, offset: startOffset + moduleNameInFile.length }, Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations, [moduleName])
214                        ]);
215                    }
216
217                    it("when project compiles from sources", () => {
218                        const host = createServerHost(filesWithSources);
219                        const session = createSessionAndOpenFile(host);
220                        verifyProjectWithUnresolvedModule(session);
221
222                        host.ensureFileOrFolder(nodeModulesRecorgnizersText);
223                        host.writeFile(recongnizerTextDistTypingFile.path, recongnizerTextDistTypingFile.content);
224                        host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
225                        host.runQueuedTimeoutCallbacks(); // Actual update
226
227                        verifyProjectWithResolvedModule(session);
228                    });
229
230                    it("when project has node_modules setup but doesnt have modules in typings folder and then recompiles", () => {
231                        const host = createServerHost([...filesWithSources, nodeModulesRecorgnizersText]);
232                        const session = createSessionAndOpenFile(host);
233                        verifyProjectWithUnresolvedModule(session);
234
235                        host.writeFile(recongnizerTextDistTypingFile.path, recongnizerTextDistTypingFile.content);
236                        host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
237                        host.runQueuedTimeoutCallbacks(); // Actual update
238
239                        if (withPathMapping) {
240                            verifyProjectWithResolvedModule(session);
241                        }
242                        else {
243                            // Cannot handle the resolution update
244                            verifyProjectWithUnresolvedModule(session);
245                        }
246                    });
247
248                    it("when project recompiles after deleting generated folders", () => {
249                        const host = createServerHost([...filesWithSources, nodeModulesRecorgnizersText, recongnizerTextDistTypingFile]);
250                        const session = createSessionAndOpenFile(host);
251
252                        verifyProjectWithResolvedModule(session);
253
254                        host.deleteFolder(recognizersTextDist, /*recursive*/ true);
255                        host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
256                        host.runQueuedTimeoutCallbacks(); // Actual update
257
258                        verifyProjectWithUnresolvedModule(session);
259
260                        host.ensureFileOrFolder(recongnizerTextDistTypingFile);
261                        host.runQueuedTimeoutCallbacks(); // Scheduled invalidation of resolutions
262                        host.runQueuedTimeoutCallbacks(); // Actual update
263
264                        if (withPathMapping) {
265                            verifyProjectWithResolvedModule(session);
266                        }
267                        else {
268                            // Cannot handle the resolution update
269                            verifyProjectWithUnresolvedModule(session);
270                        }
271                    });
272                });
273            }
274
275            verifyModuleResolution(/*withPathMapping*/ false);
276            verifyModuleResolution(/*withPathMapping*/ true);
277        });
278    });
279}
280