Skip to content

Commit a757e84

Browse files
author
Andy
authored
Add hash of project file location to project info telemetry (#16397)
* Add hash of project file location to project info telemetry * Rename to projectId
1 parent 7796e37 commit a757e84

File tree

7 files changed

+40
-28
lines changed

7 files changed

+40
-28
lines changed

src/compiler/sys.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ namespace ts {
3535
getDirectories(path: string): string[];
3636
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[];
3737
getModifiedTime?(path: string): Date;
38+
/**
39+
* This should be cryptographically secure.
40+
* A good implementation is node.js' `crypto.createHash`. (https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm)
41+
*/
3842
createHash?(data: string): string;
3943
getMemoryUsage?(): number;
4044
exit(exitCode?: number): void;

src/harness/harnessLanguageService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ namespace Harness.LanguageService {
731731
}
732732

733733
createHash(s: string) {
734-
return s;
734+
return mockHash(s);
735735
}
736736

737737
require(_initialDir: string, _moduleName: string): ts.server.RequireResult {
@@ -856,4 +856,8 @@ namespace Harness.LanguageService {
856856
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
857857
getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
858858
}
859+
860+
export function mockHash(s: string): string {
861+
return `hash-${s}`;
862+
}
859863
}

src/harness/unittests/cachingInServerLSHost.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ namespace ts {
4747
clearTimeout,
4848
setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0),
4949
clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout,
50-
createHash: s => s
50+
createHash: Harness.LanguageService.mockHash,
5151
};
5252
}
5353

src/harness/unittests/session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace ts.server {
2525
clearTimeout: noop,
2626
setImmediate: () => 0,
2727
clearImmediate: noop,
28-
createHash: s => s
28+
createHash: Harness.LanguageService.mockHash,
2929
};
3030

3131
const mockLogger: Logger = {

src/harness/unittests/telemetry.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace ts.projectSystem {
99
et.service.openClientFile(file.path);
1010
assert.equal(et.getEvents().length, 0);
1111
});
12+
1213
it("only sends an event once", () => {
1314
const file = makeFile("/a.ts");
1415
const tsconfig = makeFile("/tsconfig.json", {});
@@ -46,12 +47,13 @@ namespace ts.projectSystem {
4647
const et = new EventTracker([file1]);
4748
const compilerOptions: ts.CompilerOptions = { strict: true };
4849

49-
const projectFileName = "foo.csproj";
50+
const projectFileName = "/hunter2/foo.csproj";
5051

5152
open();
5253

5354
// TODO: Apparently compilerOptions is mutated, so have to repeat it here!
5455
et.assertProjectInfoTelemetryEvent({
56+
projectId: Harness.LanguageService.mockHash("/hunter2/foo.csproj"),
5557
compilerOptions: { strict: true },
5658
compileOnSave: true,
5759
// These properties can't be present for an external project, so they are undefined instead of false.
@@ -195,6 +197,7 @@ namespace ts.projectSystem {
195197
const et = new EventTracker([jsconfig, file]);
196198
et.service.openClientFile(file.path);
197199
et.assertProjectInfoTelemetryEvent({
200+
projectId: Harness.LanguageService.mockHash("/jsconfig.json"),
198201
fileStats: fileStats({ js: 1 }),
199202
compilerOptions: autoJsCompilerOptions,
200203
typeAcquisition: {
@@ -214,6 +217,7 @@ namespace ts.projectSystem {
214217
et.service.openClientFile(file.path);
215218
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent, /*mayBeMore*/ true);
216219
et.assertProjectInfoTelemetryEvent({
220+
projectId: Harness.LanguageService.mockHash("/jsconfig.json"),
217221
fileStats: fileStats({ js: 1 }),
218222
compilerOptions: autoJsCompilerOptions,
219223
configFileName: "jsconfig.json",
@@ -248,7 +252,26 @@ namespace ts.projectSystem {
248252
}
249253

250254
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>): void {
251-
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), makePayload(partial));
255+
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(ts.server.ProjectInfoTelemetryEvent), {
256+
projectId: Harness.LanguageService.mockHash("/tsconfig.json"),
257+
fileStats: fileStats({ ts: 1 }),
258+
compilerOptions: {},
259+
extends: false,
260+
files: false,
261+
include: false,
262+
exclude: false,
263+
compileOnSave: false,
264+
typeAcquisition: {
265+
enable: false,
266+
exclude: false,
267+
include: false,
268+
},
269+
configFileName: "tsconfig.json",
270+
projectType: "configured",
271+
languageServiceEnabled: true,
272+
version: ts.version,
273+
...partial,
274+
});
252275
}
253276

254277
getEvent<T extends server.ProjectServiceEvent>(eventName: T["eventName"], mayBeMore = false): T["data"] {
@@ -260,28 +283,6 @@ namespace ts.projectSystem {
260283
}
261284
}
262285

263-
function makePayload(partial: Partial<server.ProjectInfoTelemetryEventData>): server.ProjectInfoTelemetryEventData {
264-
return {
265-
fileStats: fileStats({ ts: 1 }),
266-
compilerOptions: {},
267-
extends: false,
268-
files: false,
269-
include: false,
270-
exclude: false,
271-
compileOnSave: false,
272-
typeAcquisition: {
273-
enable: false,
274-
exclude: false,
275-
include: false,
276-
},
277-
configFileName: "tsconfig.json",
278-
projectType: "configured",
279-
languageServiceEnabled: true,
280-
version: ts.version,
281-
...partial
282-
};
283-
}
284-
285286
function makeFile(path: string, content: {} = ""): projectSystem.FileOrFolder {
286287
return { path, content: typeof content === "string" ? "" : JSON.stringify(content) };
287288
}

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ namespace ts.projectSystem {
472472
}
473473

474474
createHash(s: string): string {
475-
return s;
475+
return Harness.LanguageService.mockHash(s);
476476
}
477477

478478
triggerDirectoryWatcherCallback(directoryName: string, fileName: string): void {

src/server/editorServices.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ namespace ts.server {
3737
}
3838

3939
export interface ProjectInfoTelemetryEventData {
40+
/** Cryptographically secure hash of project file location. */
41+
readonly projectId: string;
4042
/** Count of file extensions seen in the project. */
4143
readonly fileStats: FileStats;
4244
/**
@@ -1049,6 +1051,7 @@ namespace ts.server {
10491051
if (!this.eventHandler) return;
10501052

10511053
const data: ProjectInfoTelemetryEventData = {
1054+
projectId: this.host.createHash(projectKey),
10521055
fileStats: countEachFileTypes(project.getScriptInfos()),
10531056
compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilerOptions()),
10541057
typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()),

0 commit comments

Comments
 (0)