@vscode/telemetry-extractor 1.20.4 → 1.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.github/dependabot.yml +11 -0
  2. package/.github/workflows/node.js.yml +2 -2
  3. package/eslint.config.js +6 -1
  4. package/out/cli-options.js +2 -1
  5. package/out/cli-options.js.map +1 -1
  6. package/out/extractor.js +7 -1
  7. package/out/extractor.js.map +1 -1
  8. package/out/lib/common-properties.js +51 -1
  9. package/out/lib/common-properties.js.map +1 -1
  10. package/out/lib/declarations.js.map +1 -1
  11. package/out/lib/event-definition.js +5 -0
  12. package/out/lib/event-definition.js.map +1 -0
  13. package/out/lib/events.js +28 -1
  14. package/out/lib/events.js.map +1 -1
  15. package/out/lib/object-converter.js +58 -4
  16. package/out/lib/object-converter.js.map +1 -1
  17. package/out/lib/operations.js +20 -1
  18. package/out/lib/operations.js.map +1 -1
  19. package/out/lib/parser.js +12 -16
  20. package/out/lib/parser.js.map +1 -1
  21. package/out/lib/ripgrep.js +14 -0
  22. package/out/lib/ripgrep.js.map +1 -0
  23. package/out/lib/save-declarations.js +11 -6
  24. package/out/lib/save-declarations.js.map +1 -1
  25. package/out/lib/ts-parser-worker.js +39 -0
  26. package/out/lib/ts-parser-worker.js.map +1 -0
  27. package/out/lib/ts-parser.js +198 -31
  28. package/out/lib/ts-parser.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/cli-options.ts +2 -1
  31. package/src/extractor.ts +8 -1
  32. package/src/lib/common-properties.ts +71 -5
  33. package/src/lib/declarations.ts +2 -1
  34. package/src/lib/event-definition.ts +7 -0
  35. package/src/lib/events.ts +33 -1
  36. package/src/lib/object-converter.ts +73 -4
  37. package/src/lib/operations.ts +20 -2
  38. package/src/lib/parser.ts +16 -30
  39. package/src/lib/ripgrep.ts +10 -0
  40. package/src/lib/save-declarations.ts +15 -9
  41. package/src/lib/ts-parser-worker.ts +38 -0
  42. package/src/lib/ts-parser.ts +242 -39
  43. package/tsconfig.json +1 -0
  44. package/vscode-telemetry-extractor.d.ts +21 -1
@@ -9,6 +9,67 @@ export class CommonProperties {
9
9
  }
10
10
  }
11
11
 
12
+ export type ColumnType =
13
+ 'bool' |
14
+ 'int' |
15
+ 'long' |
16
+ 'real' |
17
+ 'decimal' |
18
+ 'dynamic' |
19
+ 'guid' |
20
+ 'string' |
21
+ 'datetime' |
22
+ 'timespan';
23
+
24
+ export namespace ColumnType {
25
+ export function fromString(type: string): ColumnType | undefined{
26
+ switch (type.toLowerCase()) {
27
+ case 'bool':
28
+ case 'boolean':
29
+ return 'bool';
30
+ case 'int':
31
+ return 'int';
32
+ case 'long':
33
+ return 'long';
34
+ case 'real':
35
+ case 'double':
36
+ return 'real';
37
+ case 'decimal':
38
+ return 'decimal';
39
+ case 'dynamic':
40
+ return 'dynamic';
41
+ case 'guid':
42
+ case 'uuid':
43
+ case 'uniqueid':
44
+ return 'guid';
45
+ case 'string':
46
+ return 'string';
47
+ case 'datetime':
48
+ case 'date':
49
+ return 'datetime';
50
+ case 'timespan':
51
+ case 'time':
52
+ return 'timespan';
53
+ default:
54
+ return undefined;
55
+ }
56
+ }
57
+ }
58
+
59
+ export type ColumnInfo = {
60
+ name?: string;
61
+ type: ColumnType;
62
+ }
63
+
64
+ export namespace ColumnInfo {
65
+ export function is(obj: unknown): obj is ColumnInfo {
66
+ const candidate = obj as ColumnInfo;
67
+ return !!candidate &&
68
+ (candidate.name === undefined || typeof candidate.name === 'string') &&
69
+ typeof candidate.type === 'string' && ColumnType.fromString(candidate.type) !== undefined;
70
+ }
71
+ }
72
+
12
73
  export class Property implements IProperty {
13
74
  public name: string;
14
75
  public classification: string;
@@ -18,16 +79,21 @@ export class Property implements IProperty {
18
79
  public expiration?: string;
19
80
  public owner?: string;
20
81
  public comment?: string;
21
-
82
+
83
+ // The name and type of the property if mirrored into a flat table in Kusto.
84
+ // Needs to be removed when generating the patch for the GDPR catalog as
85
+ // flat table properties are not allowed in the catalog.
86
+ public column?: ColumnInfo;
87
+
22
88
  constructor (
23
- name: string,
89
+ name: string,
24
90
  classification: string,
25
- purpose: string,
91
+ purpose: string,
26
92
  expiration?: string,
27
93
  owner?: string,
28
94
  comment?: string,
29
- endpoint?: string,
30
- isMeasurement?: boolean
95
+ endpoint?: string,
96
+ isMeasurement?: boolean,
31
97
  ) {
32
98
  this.name = name;
33
99
  this.classification = classification;
@@ -3,7 +3,7 @@
3
3
  import { Fragments } from "./fragments";
4
4
  import { Events, Include, Inline } from "./events";
5
5
  import { CommonProperties, Property } from "./common-properties";
6
- import type { Events as OutputEvents, CommonProperties as OutputCommonProperties } from "../../vscode-telemetry-extractor";
6
+ import type { Events as OutputEvents, CommonProperties as OutputCommonProperties , TableInfo as OutputTableInfo } from "../../vscode-telemetry-extractor";
7
7
 
8
8
  export interface Declarations {
9
9
  fragments: Fragments;
@@ -15,6 +15,7 @@ export interface Declarations {
15
15
  export interface OutputtedDeclarations {
16
16
  events: OutputEvents;
17
17
  commonProperties: OutputCommonProperties;
18
+ tableInfos: { [key: string]: OutputTableInfo };
18
19
  }
19
20
 
20
21
  function resolveIncludes(target: Events | Fragments, fragments: Fragments) {
@@ -0,0 +1,7 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+
4
+ export interface EventDefinition {
5
+ properties: Record<string, unknown>;
6
+ location: string;
7
+ }
package/src/lib/events.ts CHANGED
@@ -11,14 +11,46 @@ export class Events implements ITelemetryData {
11
11
  }
12
12
  }
13
13
 
14
+ export type TableInfo = {
15
+ name: string;
16
+ commonProperties: 'standard';
17
+ backfill: boolean | string;
18
+ }
19
+ export namespace TableInfo {
20
+ export function fromObject(obj: unknown): TableInfo | undefined {
21
+ if (typeof obj !== 'object' || obj === null) {
22
+ return undefined;
23
+ }
24
+ const candidate: Partial<TableInfo> = obj as TableInfo;
25
+ if (typeof candidate.name !== 'string') {
26
+ return undefined;
27
+ }
28
+ const result: TableInfo = {
29
+ name: candidate.name,
30
+ commonProperties: 'standard',
31
+ backfill: false
32
+
33
+ };
34
+ if (candidate.commonProperties === 'standard') {
35
+ result.commonProperties = 'standard';
36
+ }
37
+ if (typeof candidate.backfill === 'boolean' || typeof candidate.backfill === 'string') {
38
+ result.backfill = candidate.backfill;
39
+ }
40
+ return result;
41
+ }
42
+ }
43
+
14
44
  export class Event implements ITelemetryDataPoint {
15
45
  public name: string;
16
46
  // It gets a little more complicated here as events can have a bunch of different things
17
47
  public properties: Array<Property | Metadata | Include | Inline | Wildcard>;
48
+ public tableInfo?: TableInfo;
18
49
  constructor (name: string) {
19
50
  this.name = name;
20
51
  this.properties = [];
21
- }
52
+ this.tableInfo = undefined;
53
+ }
22
54
  }
23
55
 
24
56
  export class Include implements IInclude {
@@ -1,25 +1,33 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT license.
3
3
  import { Declarations, OutputtedDeclarations } from "./declarations";
4
- import { Property } from "./common-properties";
5
- import { Metadata, Wildcard } from "./events";
4
+ import { ColumnType, Property, ColumnInfo } from "./common-properties";
5
+ import { Metadata, Wildcard, TableInfo as JsonTableInfo } from "./events";
6
6
  import * as keywords from './keywords';
7
7
 
8
8
  // Converts the declarations array to an object format for easy readability.
9
9
 
10
+ type TableInfo = OutputtedDeclarations['tableInfos'][string];
11
+ type BagInfo = TableInfo['columns'][number]['bag'];
12
+
10
13
  export async function transformOutput(output: Declarations): Promise<OutputtedDeclarations> {
11
14
  // If there's no events or common properties, we emit a null object
12
15
  if (output.events.dataPoints.length === 0 && output.commonProperties.properties.length === 0) {
13
- return { events: Object.create(null), commonProperties: Object.create(null) };
16
+ return { events: Object.create(null), commonProperties: Object.create(null), tableInfos: {} };
14
17
  }
15
18
  const newEvents = Object.create(null);
16
19
  const oldEvents = output.events.dataPoints;
20
+ const tableInfos: { [key: string]: TableInfo } = {};
17
21
  for (const event of oldEvents) {
18
22
  // Check if event.name ends with a number, if so throw an error because we don't support event names which end with numbers
19
23
  if (/\d$/.test(event.name)) {
20
24
  throw new Error(`Event name ${event.name} ends with a number. Event names cannot end with numbers.`);
21
25
  }
22
26
  newEvents[event.name] = Object.create(null);
27
+ const tableInfo: TableInfo | undefined = event.tableInfo ? { ...event.tableInfo, columns: [] } : undefined;
28
+ if (tableInfo) {
29
+ tableInfos[tableInfo.name] = tableInfo;
30
+ }
23
31
  //We know there won't be anymore includes or inlines because we have resolved them
24
32
  for (const property of event.properties as Array<Property | Wildcard | Metadata>) {
25
33
  if (property instanceof Wildcard) {
@@ -66,11 +74,18 @@ export async function transformOutput(output: Declarations): Promise<OutputtedDe
66
74
  if (property.isMeasurement) {
67
75
  newEvents[event.name][propetyNameChanger(property.name)]['isMeasurement'] = property.isMeasurement;
68
76
  }
77
+ if (tableInfo !== undefined && property.column) {
78
+ const name = property.column.name ?? property.name;
79
+ tableInfo.columns.push({ name, type: property.column.type, bag: { name: property.name.toLowerCase(), store: property.isMeasurement === true ? 'Measures' : 'Properties' } });
80
+ }
69
81
  } else {
70
82
  // Comments, expiration, and owner metadata are handled here
71
83
  newEvents[event.name][propetyNameChanger(property.name)] = property.value;
72
84
  }
73
85
  }
86
+ if (tableInfo !== undefined) {
87
+ tableInfos[tableInfo.name] = tableInfo;
88
+ }
74
89
  }
75
90
  const newCommonProperties = Object.create(null);
76
91
  const oldCommonProperties = output.commonProperties.properties;
@@ -89,8 +104,62 @@ export async function transformOutput(output: Declarations): Promise<OutputtedDe
89
104
  newCommonProperties[propetyNameChanger(property.name)]['isMeasurement'] = property.isMeasurement;
90
105
  }
91
106
  }
92
- return { events: newEvents, commonProperties: newCommonProperties };
107
+ return { events: newEvents, commonProperties: newCommonProperties, tableInfos };
108
+ }
109
+
110
+ type TypeScriptPropertyDeclaration = {
111
+ name?: string;
112
+ isMeasurement?: boolean;
113
+ type?: string;
114
+ column?: { name?: string; type?: string }
93
115
  }
116
+ type TypeScriptEventDeclaration = {
117
+ $tableInfo?: unknown;
118
+ [name: string]: TypeScriptPropertyDeclaration | unknown;
119
+ };
120
+
121
+ export function transformTypeScriptDeclaration(declaration: TypeScriptEventDeclaration): { declaration: object, tableInfo: TableInfo | undefined} {
122
+ // The property names in the TS events are already all lowercased.
123
+ const tableInfoProperty = declaration['$tableinfo'];
124
+ let tableInfo: TableInfo | undefined = undefined;
125
+ if (tableInfoProperty !== undefined) {
126
+ delete declaration['$tableinfo'];
127
+ const json = JsonTableInfo.fromObject(tableInfoProperty);
128
+ if (json !== undefined) {
129
+ tableInfo = { name: json.name, commonProperties: json.commonProperties, backfill: json.backfill, columns: [] };
130
+ }
131
+ }
132
+ for (const name of Object.keys(declaration)) {
133
+ if (name === '$tableinfo') {
134
+ continue;
135
+ }
136
+ const property = declaration[name] as TypeScriptPropertyDeclaration;
137
+ if (!property || typeof property !== 'object') {
138
+ continue;
139
+ }
140
+ if (tableInfo !== undefined) {
141
+ if (property.column !== undefined) {
142
+ if (ColumnInfo.is(property.column)) {
143
+ const type = ColumnType.fromString(property.column.type);
144
+ if (type !== undefined) {
145
+ const bag: BagInfo = { store: property.isMeasurement === true ? 'Measures' : 'Properties', name: name };
146
+ tableInfo.columns.push({ name: property.column.name ?? name, type: type, bag });
147
+ }
148
+ }
149
+ } else if (typeof property.type === 'string') {
150
+ const columnType = ColumnType.fromString(property.type);
151
+ if (columnType) {
152
+ const bag: BagInfo = { store: property.isMeasurement === true ? 'Measures' : 'Properties', name: name };
153
+ tableInfo.columns.push({ name: name, type: columnType, bag });
154
+ }
155
+ }
156
+ }
157
+ delete property.type;
158
+ delete property.column;
159
+ }
160
+ return { declaration, tableInfo };
161
+ }
162
+
94
163
 
95
164
  function propetyNameChanger(name: string) {
96
165
  name = name.toLowerCase();
@@ -2,7 +2,7 @@
2
2
  // Licensed under the MIT license.
3
3
  import { Fragments, Fragment } from "./fragments";
4
4
  import { Events, Event, Include, Inline, Wildcard, WildcardEntry, Metadata } from "./events";
5
- import { Property } from "./common-properties";
5
+ import { ColumnType, ColumnInfo, Property } from "./common-properties";
6
6
  import * as keywords from './keywords';
7
7
  import * as path from 'path';
8
8
 
@@ -17,6 +17,9 @@ export function merge(target: Fragments | Events, source: Fragments | Events) {
17
17
  if (!eventsAreCompatible(found, item)) {
18
18
  continue;
19
19
  }
20
+ if (found.tableInfo === undefined) {
21
+ found.tableInfo = item.tableInfo;
22
+ }
20
23
  // Merge unique properties from source into target
21
24
  for (const prop of item.properties) {
22
25
  if (prop instanceof Property) {
@@ -83,7 +86,7 @@ function eventsAreCompatible(left: Event, right: Event): boolean {
83
86
  return true;
84
87
  }
85
88
 
86
- // Searches the object for an event or fragment of the specific name
89
+ // Searches the object for an event or fragment of the specific name
87
90
  // If found returns, if not found creates it, places it in the array, and then returns
88
91
  export function findOrCreate(searchTarget: Events | Fragments, name: string) {
89
92
  let found = searchTarget.dataPoints.find((item) => {
@@ -122,6 +125,10 @@ export function mergeWildcards(wildcard: any[], target: Event | Fragment, applyE
122
125
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
126
  export function populateProperties(properties: any, target: Event | Fragment, applyEndpoints = false) {
124
127
  for (const propertyName in properties) {
128
+ // Skip the `$tableInfo` property since it is an artificial property for the flat table support.
129
+ if (propertyName === '$tableInfo') {
130
+ continue;
131
+ }
125
132
  const currentProperty = properties[propertyName];
126
133
  if (propertyName === keywords.include) {
127
134
  target.properties.push(new Include(currentProperty));
@@ -142,6 +149,17 @@ export function populateProperties(properties: any, target: Event | Fragment, ap
142
149
  if (currentProperty.isMeasurement) {
143
150
  prop.isMeasurement = currentProperty.isMeasurement;
144
151
  }
152
+ if (typeof currentProperty.type === 'string') {
153
+ const columnType = ColumnType.fromString(currentProperty.type);
154
+ if (columnType) {
155
+ prop.column = { type: columnType };
156
+ }
157
+ } else if (ColumnInfo.is(currentProperty.column)) {
158
+ const columnType = ColumnType.fromString(currentProperty.column.type);
159
+ if (columnType) {
160
+ prop.column = { name: currentProperty.column.name, type: columnType };
161
+ }
162
+ }
145
163
  target.properties.push(prop);
146
164
  }
147
165
  }
package/src/lib/parser.ts CHANGED
@@ -7,19 +7,11 @@ import * as fs from 'fs';
7
7
  // Not importing 'process' as tsc claims `process.exitCode` is read-only when it actually is not.
8
8
  import { Fragments } from './fragments';
9
9
  import { Property, CommonProperties } from './common-properties';
10
- import { Events } from './events';
10
+ import { Events, Event, TableInfo } from './events';
11
11
  import { Declarations } from './declarations';
12
12
  import { merge, findOrCreate, populateProperties, makeExclusionsRelativeToSource } from './operations';
13
-
14
- export interface EventPropertySignature {
15
- classification: string;
16
- purpose: string;
17
- }
18
-
19
- export interface EventDefinition {
20
- properties: Record<string, EventPropertySignature>;
21
- location: string;
22
- }
13
+ import { parseRipgrepFilePaths } from './ripgrep';
14
+ import { EventDefinition } from './event-definition';
23
15
 
24
16
  export class Parser {
25
17
 
@@ -137,7 +129,6 @@ export class Parser {
137
129
 
138
130
  private findEvents(sourceDir: string) {
139
131
  const filesWithEvents = this.asAbsoluteFilePaths(this.findFilesWithEvents(sourceDir));
140
- const seenEvents = new Set<string>();
141
132
 
142
133
  // Using [\s\S]* instead of .* since the latter does not match when using /m option
143
134
  const eventMatcher = /\/\*\s*__GDPR__\b([\s\S]*?)\*\//mg;
@@ -152,14 +143,18 @@ export class Parser {
152
143
  const lineNumber = this.getLineNumber(fileContents, match.index);
153
144
  this.addEventDefinition(eventName, conflictProperties, `${filePath}:${lineNumber}`);
154
145
 
155
- if (seenEvents.has(eventName)) {
156
- return;
146
+ const event = new Event(eventName);
147
+ if (event instanceof Event && eventProperties['$tableInfo'] !== undefined) {
148
+ const tableInfo: TableInfo | undefined = TableInfo.fromObject(eventProperties['$tableInfo']);
149
+ if (tableInfo) {
150
+ event.tableInfo = tableInfo;
151
+ }
157
152
  }
158
-
159
- seenEvents.add(eventName);
160
- const event = findOrCreate(eventDeclarations, eventName);
161
153
  // Get the propeties which the event possesses
162
154
  populateProperties(eventProperties, event, this.applyEndpoints);
155
+ const currentDeclaration = new Events();
156
+ currentDeclaration.dataPoints.push(event);
157
+ merge(eventDeclarations, currentDeclaration);
163
158
  } catch (error) {
164
159
  console.error(`Event Declaration Error: ${error} in file ${filePath}`);
165
160
  console.error(`Source comment:\n${match[0]}`);
@@ -177,23 +172,14 @@ export class Parser {
177
172
  return definitions;
178
173
  }
179
174
 
180
- private addEventDefinition(eventName: string, properties: Record<string, EventPropertySignature>, location: string) {
175
+ private addEventDefinition(eventName: string, properties: Record<string, unknown>, location: string) {
181
176
  const existing = this.eventDefinitions.get(eventName) ?? [];
182
177
  existing.push({ properties, location });
183
178
  this.eventDefinitions.set(eventName, existing);
184
179
  }
185
180
 
186
- private extractConflictProperties(eventProperties: Record<string, unknown>): Record<string, EventPropertySignature> {
187
- const result: Record<string, EventPropertySignature> = {};
188
- for (const [key, value] of Object.entries(eventProperties)) {
189
- if (value && typeof value === 'object' && !Array.isArray(value)) {
190
- const obj = value as Record<string, unknown>;
191
- if (typeof obj.classification === 'string' && typeof obj.purpose === 'string') {
192
- result[key] = { classification: obj.classification, purpose: obj.purpose };
193
- }
194
- }
195
- }
196
- return result;
181
+ private extractConflictProperties(eventProperties: Record<string, unknown>): Record<string, unknown> {
182
+ return { ...eventProperties };
197
183
  }
198
184
 
199
185
  private getLineNumber(fileContents: string, index: number | undefined) {
@@ -210,7 +196,7 @@ export class Parser {
210
196
  const ripgrepArgs = ['--files-with-matches', '--glob', '*.ts', '--glob', '*.tsx', '--glob', '*.cs', ...exclusions, '--regexp', ripgrepPattern, '--', sourceDir];
211
197
  try {
212
198
  const filePaths = cp.execFileSync(rgPath, ripgrepArgs, { encoding: 'ascii', cwd: `${sourceDir}` });
213
- return filePaths.split(/(?:\r\n|\r|\n)/g).filter(path => path && path.length > 0);
199
+ return parseRipgrepFilePaths(filePaths);
214
200
  } catch {
215
201
  // ripgrep's return code != 0 if there are no matches
216
202
  return [];
@@ -0,0 +1,10 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+
4
+ export function parseRipgrepFilePaths(output: string): string[] {
5
+ // Sorting after ripgrep exits preserves its parallel filesystem traversal.
6
+ return output
7
+ .split(/(?:\r\n|\r|\n)/g)
8
+ .filter(filePath => filePath.length > 0)
9
+ .sort();
10
+ }
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import { Parser } from './parser';
5
5
  import * as fileWriter from './file-writer';
6
6
  import { resolveDeclarations, OutputtedDeclarations, Declarations } from './declarations';
7
- import { transformOutput } from './object-converter';
7
+ import { transformOutput, transformTypeScriptDeclaration } from './object-converter';
8
8
  import { Events } from './events';
9
9
  import { CommonProperties } from './common-properties';
10
10
  import { TsParser } from './ts-parser';
@@ -12,10 +12,10 @@ import { patchDebugEvents } from './debug-patch';
12
12
  import { ParserOptions, SourceSpec } from './source-spec';
13
13
  import { logMessage } from './logger';
14
14
  import { Fragments } from './fragments';
15
- import { EventDefinition, EventPropertySignature } from './parser';
15
+ import { EventDefinition } from './event-definition';
16
16
 
17
17
  interface EventConflictEntry {
18
- properties: Record<string, EventPropertySignature>;
18
+ properties: Record<string, unknown>;
19
19
  location: string;
20
20
  source: 'GDPR' | 'TS';
21
21
  }
@@ -96,7 +96,7 @@ function reportDuplicateEventConflicts(definitions: Map<string, EventConflictEnt
96
96
  continue;
97
97
  }
98
98
 
99
- // Check if any overlapping properties have different classification/purpose
99
+ // Compatible declarations may add properties, but must agree on every overlapping output field.
100
100
  let eventHasConflict = false;
101
101
  for (let i = 0; i < entries.length && !eventHasConflict; i++) {
102
102
  for (let j = i + 1; j < entries.length && !eventHasConflict; j++) {
@@ -104,7 +104,7 @@ function reportDuplicateEventConflicts(definitions: Map<string, EventConflictEnt
104
104
  if (propName in entries[j].properties) {
105
105
  const a = entries[i].properties[propName];
106
106
  const b = entries[j].properties[propName];
107
- if (a.classification !== b.classification || a.purpose !== b.purpose) {
107
+ if (JSON.stringify(deepSortKeys(a)) !== JSON.stringify(deepSortKeys(b))) {
108
108
  eventHasConflict = true;
109
109
  break;
110
110
  }
@@ -135,11 +135,11 @@ export async function extractAndResolveDeclarations(sourceSpecs: Array<SourceSpe
135
135
  declarations = resolveDeclarations(declarations, spec.parserOptions.verbose);
136
136
  let typescriptDeclarations = Object.create(null);
137
137
  // The parser does not know how to handle multiple source directories due to different TS configs, so we manually have to parse each source dir
138
- spec.sourceDirs.forEach((dir) => {
138
+ for (const dir of spec.sourceDirs) {
139
139
  const tsParser = new TsParser(dir, spec.excludedDirs, spec.parserOptions.applyEndpoints, spec.parserOptions.lowerCaseEvents);
140
- Object.assign(typescriptDeclarations, tsParser.parseFiles());
140
+ Object.assign(typescriptDeclarations, await tsParser.parseFiles());
141
141
  mergeEventDefinitions(allEventDefinitions, tsParser.getEventDefinitions(), 'TS', spec.parserOptions.eventPrefix);
142
- });
142
+ }
143
143
  if (spec.parserOptions.eventPrefix !== '') {
144
144
  declarations.events.dataPoints = declarations.events.dataPoints.map((event) => {
145
145
  event.name = spec.parserOptions.eventPrefix + event.name;
@@ -170,7 +170,12 @@ export async function extractAndResolveDeclarations(sourceSpecs: Array<SourceSpe
170
170
  if (formattedDeclarations.events === undefined) {
171
171
  formattedDeclarations.events = Object.create(null);
172
172
  }
173
- formattedDeclarations.events[dec] = allTypeScriptDeclarations[dec];
173
+ const transformed = transformTypeScriptDeclaration(allTypeScriptDeclarations[dec]);
174
+ formattedDeclarations.events[dec] = transformed.declaration as OutputtedDeclarations['events'][string];
175
+ if (transformed.tableInfo) {
176
+ const tableInfo = transformed.tableInfo;
177
+ formattedDeclarations.tableInfos[tableInfo.name] = tableInfo;
178
+ }
174
179
  }
175
180
  const hasPropertyValidationErrors = validateOutputtedDeclarations(formattedDeclarations);
176
181
  if (hasDuplicateEventConflicts || hasPropertyValidationErrors || process.exitCode === 1) {
@@ -182,3 +187,4 @@ export async function extractAndResolveDeclarations(sourceSpecs: Array<SourceSpe
182
187
  return Promise.reject(error);
183
188
  }
184
189
  }
190
+
@@ -0,0 +1,38 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { parentPort, workerData } from 'worker_threads';
4
+ import { Project } from 'ts-morph';
5
+ import { TsProjectParser, ParserWorkerRequest, ParserWorkerResult } from './ts-parser';
6
+
7
+ if (!parentPort) {
8
+ throw new Error('Telemetry parser worker requires a parent');
9
+ }
10
+
11
+ const request: ParserWorkerRequest = workerData;
12
+ const project = new Project({ compilerOptions: request.compilerOptions });
13
+ let result: ParserWorkerResult;
14
+
15
+ if (request.kind === 'prepare') {
16
+ for (const file of request.sourceFiles) {
17
+ project.addSourceFileAtPathIfExists(file);
18
+ }
19
+ result = new TsProjectParser(project, false, false).prepare();
20
+ } else if (request.kind === 'parse') {
21
+ // Keep global declarations and module augmentations visible in every batch.
22
+ for (const file of request.sharedSourceFiles) {
23
+ project.addSourceFileAtPath(file);
24
+ }
25
+ for (const group of request.calls) {
26
+ project.addSourceFileAtPath(group.filePath);
27
+ }
28
+ const parser = new TsProjectParser(project, request.applyEndpoints, request.lowerCaseEvents, request.definitions);
29
+ result = {
30
+ kind: 'parsed',
31
+ events: parser.parseFiles(request.calls, request.events),
32
+ definitions: [...parser.getEventDefinitions()]
33
+ };
34
+ } else {
35
+ throw new Error('Unknown telemetry parser worker request');
36
+ }
37
+
38
+ parentPort.postMessage(result);