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