• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.projectSystem {
2    describe("unittests:: tsserver:: project telemetry", () => {
3        it("does nothing for inferred project", () => {
4            const file = makeFile("/a.js");
5            const et = new TestServerEventManager([file]);
6            et.service.openClientFile(file.path);
7            et.hasZeroEvent(server.ProjectInfoTelemetryEvent);
8        });
9
10        it("only sends an event once", () => {
11            const file = makeFile("/a/a.ts");
12            const file2 = makeFile("/b.ts");
13            const tsconfig = makeFile("/a/tsconfig.json", {});
14
15            const et = new TestServerEventManager([file, file2, tsconfig]);
16            et.service.openClientFile(file.path);
17            et.assertProjectInfoTelemetryEvent({}, tsconfig.path);
18
19            et.service.closeClientFile(file.path);
20            checkNumberOfProjects(et.service, { configuredProjects: 1 });
21
22            et.service.openClientFile(file2.path);
23            checkNumberOfProjects(et.service, { inferredProjects: 1 });
24
25            et.hasZeroEvent(server.ProjectInfoTelemetryEvent);
26
27            et.service.openClientFile(file.path);
28            checkNumberOfProjects(et.service, { configuredProjects: 1, inferredProjects: 1 });
29
30            et.hasZeroEvent(server.ProjectInfoTelemetryEvent);
31        });
32
33        it("counts files by extension", () => {
34            const files = ["ts.ts", "tsx.tsx", "moo.ts", "dts.d.ts", "jsx.jsx", "js.js", "badExtension.badExtension"].map(f => makeFile(`/src/${f}`));
35            const notIncludedFile = makeFile("/bin/ts.js");
36            const compilerOptions: CompilerOptions = { allowJs: true };
37            const tsconfig = makeFile("/tsconfig.json", { compilerOptions, include: ["src"] });
38
39            const et = new TestServerEventManager([...files, notIncludedFile, tsconfig]);
40            et.service.openClientFile(files[0].path);
41            et.assertProjectInfoTelemetryEvent({
42                fileStats: fileStats({ ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 }),
43                compilerOptions,
44                include: true,
45            });
46        });
47
48        it("works with external project", () => {
49            const file1 = makeFile("/a.ts");
50            const et = new TestServerEventManager([file1]);
51            const compilerOptions: server.protocol.CompilerOptions = { strict: true };
52
53            const projectFileName = "/hunter2/foo.csproj";
54
55            open();
56
57            // TODO: Apparently compilerOptions is mutated, so have to repeat it here!
58            et.assertProjectInfoTelemetryEvent({
59                compilerOptions: { strict: true },
60                compileOnSave: true,
61                // These properties can't be present for an external project, so they are undefined instead of false.
62                extends: undefined,
63                files: undefined,
64                include: undefined,
65                exclude: undefined,
66                configFileName: "other",
67                projectType: "external",
68            }, "/hunter2/foo.csproj");
69
70            // Also test that opening an external project only sends an event once.
71            et.service.closeClientFile(file1.path);
72
73            et.service.closeExternalProject(projectFileName);
74            checkNumberOfProjects(et.service, { externalProjects: 0 });
75
76            open();
77            assert.equal(et.getEvents().length, 0);
78
79            function open(): void {
80                et.service.openExternalProject({
81                    rootFiles: toExternalFiles([file1.path]),
82                    options: compilerOptions,
83                    projectFileName,
84                });
85                checkNumberOfProjects(et.service, { externalProjects: 1 });
86                et.service.openClientFile(file1.path); // Only on file open the project will be updated
87            }
88        });
89
90        it("does not expose paths", () => {
91            const file = makeFile("/a.ts");
92
93            const compilerOptions: CompilerOptions = {
94                project: "",
95                outFile: "hunter2.js",
96                outDir: "hunter2",
97                rootDir: "hunter2",
98                baseUrl: "hunter2",
99                rootDirs: ["hunter2"],
100                typeRoots: ["hunter2"],
101                types: ["hunter2"],
102                sourceRoot: "hunter2",
103                mapRoot: "hunter2",
104                jsxFactory: "hunter2",
105                out: "hunter2",
106                reactNamespace: "hunter2",
107                charset: "hunter2",
108                locale: "hunter2",
109                declarationDir: "hunter2",
110                paths: {
111                    "*": ["hunter2"],
112                },
113
114                // Boolean / number options get through
115                declaration: true,
116
117                // List of string enum gets through -- but only if legitimately a member of the enum
118                lib: ["es6", "dom", "hunter2"],
119
120                // Sensitive data doesn't get through even if sent to an option of safe type
121                checkJs: "hunter2" as any as boolean,
122            };
123            const safeCompilerOptions: CompilerOptions = {
124                project: "",
125                outFile: "",
126                outDir: "",
127                rootDir: "",
128                baseUrl: "",
129                rootDirs: [""],
130                typeRoots: [""],
131                types: [""],
132                sourceRoot: "",
133                mapRoot: "",
134                jsxFactory: "",
135                out: "",
136                reactNamespace: "",
137                charset: "",
138                locale: "",
139                declarationDir: "",
140                paths: "" as any,
141
142                declaration: true,
143
144                lib: ["es6", "dom"],
145            };
146            (compilerOptions as any).unknownCompilerOption = "hunter2"; // These are always ignored.
147            const tsconfig = makeFile("/tsconfig.json", { compilerOptions, files: ["/a.ts"] });
148
149            const et = new TestServerEventManager([file, tsconfig]);
150            et.service.openClientFile(file.path);
151
152            et.assertProjectInfoTelemetryEvent({
153                compilerOptions: safeCompilerOptions,
154                files: true,
155            });
156        });
157
158        it("sends telemetry for extends, files, include, exclude, and compileOnSave", () => {
159            const file = makeFile("/hunter2/a.ts");
160            const tsconfig = makeFile("/tsconfig.json", {
161                compilerOptions: {},
162                extends: "hunter2.json",
163                files: ["hunter2/a.ts"],
164                include: ["hunter2"],
165                exclude: ["hunter2"],
166                compileOnSave: true,
167            });
168
169            const et = new TestServerEventManager([tsconfig, file]);
170            et.service.openClientFile(file.path);
171            et.assertProjectInfoTelemetryEvent({
172                extends: true,
173                files: true,
174                include: true,
175                exclude: true,
176                compileOnSave: true,
177            });
178        });
179
180        const autoJsCompilerOptions = {
181            // Apparently some options are added by default.
182            allowJs: true,
183            allowSyntheticDefaultImports: true,
184            maxNodeModuleJsDepth: 2,
185            skipLibCheck: true,
186            noEmit: true
187        };
188
189        it("sends telemetry for typeAcquisition settings", () => {
190            const file = makeFile("/a.js");
191            const jsconfig = makeFile("/jsconfig.json", {
192                compilerOptions: {},
193                typeAcquisition: {
194                    enable: true,
195                    enableAutoDiscovery: false,
196                    include: ["hunter2", "hunter3"],
197                    exclude: [],
198                },
199            });
200            const et = new TestServerEventManager([jsconfig, file]);
201            et.service.openClientFile(file.path);
202            et.assertProjectInfoTelemetryEvent({
203                fileStats: fileStats({ js: 1 }),
204                compilerOptions: autoJsCompilerOptions,
205                typeAcquisition: {
206                    enable: true,
207                    include: true,
208                    exclude: false,
209                },
210                configFileName: "jsconfig.json",
211            }, "/jsconfig.json");
212        });
213
214        it("sends telemetry for file sizes", () => {
215            const jsFile = makeFile("/a.js", "1");
216            const tsFile = makeFile("/b.ts", "12");
217            const tsconfig = makeFile("/jsconfig.json", {
218                compilerOptions: autoJsCompilerOptions
219            });
220            const et = new TestServerEventManager([tsconfig, jsFile, tsFile]);
221            et.service.openClientFile(jsFile.path);
222            et.assertProjectInfoTelemetryEvent({
223                fileStats: fileStats({ js: 1, jsSize: 1, ts: 1, tsSize: 2 }),
224                compilerOptions: autoJsCompilerOptions,
225                typeAcquisition: {
226                    enable: true,
227                    include: false,
228                    exclude: false,
229                },
230                configFileName: "jsconfig.json",
231            }, "/jsconfig.json");
232        });
233
234        it("detects whether language service was disabled", () => {
235            const file = makeFile("/a.js");
236            const tsconfig = makeFile("/jsconfig.json", {});
237            const et = new TestServerEventManager([tsconfig, file]);
238            const fileSize = server.maxProgramSizeForNonTsFiles + 1;
239            et.host.getFileSize = () => fileSize;
240            et.service.openClientFile(file.path);
241            et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent);
242            et.assertProjectInfoTelemetryEvent({
243                fileStats: fileStats({ js: 1, jsSize: fileSize }),
244                compilerOptions: autoJsCompilerOptions,
245                configFileName: "jsconfig.json",
246                typeAcquisition: {
247                    enable: true,
248                    include: false,
249                    exclude: false,
250                },
251                languageServiceEnabled: false,
252            }, "/jsconfig.json");
253        });
254
255        describe("open files telemetry", () => {
256            it("sends event for inferred project", () => {
257                const ajs = makeFile("/a.js", "// @ts-check\nconst x = 0;");
258                const bjs = makeFile("/b.js");
259                const et = new TestServerEventManager([ajs, bjs]);
260
261                et.service.openClientFile(ajs.path);
262                et.assertOpenFileTelemetryEvent({ checkJs: true });
263
264                et.service.openClientFile(bjs.path);
265                et.assertOpenFileTelemetryEvent({ checkJs: false });
266
267                // No repeated send for opening a file seen before.
268                et.service.openClientFile(bjs.path);
269                et.assertNoOpenFilesTelemetryEvent();
270            });
271
272            it("not for '.ts' file", () => {
273                const ats = makeFile("/a.ts", "");
274                const et = new TestServerEventManager([ats]);
275
276                et.service.openClientFile(ats.path);
277                et.assertNoOpenFilesTelemetryEvent();
278            });
279
280            it("even for project with 'ts-check' in config", () => {
281                const file = makeFile("/a.js");
282                const compilerOptions: CompilerOptions = { checkJs: true };
283                const jsconfig = makeFile("/jsconfig.json", { compilerOptions });
284                const et = new TestServerEventManager([jsconfig, file]);
285                et.service.openClientFile(file.path);
286                et.assertOpenFileTelemetryEvent({ checkJs: false });
287            });
288        });
289    });
290
291    function makeFile(path: string, content: {} = ""): File {
292        return { path, content: isString(content) ? content : JSON.stringify(content) };
293    }
294}
295