@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.
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/node.js.yml +2 -2
- package/eslint.config.js +6 -1
- package/out/cli-options.js +2 -1
- package/out/cli-options.js.map +1 -1
- package/out/extractor.js +7 -1
- package/out/extractor.js.map +1 -1
- package/out/lib/common-properties.js +51 -1
- package/out/lib/common-properties.js.map +1 -1
- package/out/lib/declarations.js.map +1 -1
- package/out/lib/event-definition.js +5 -0
- package/out/lib/event-definition.js.map +1 -0
- package/out/lib/events.js +28 -1
- package/out/lib/events.js.map +1 -1
- package/out/lib/object-converter.js +58 -4
- package/out/lib/object-converter.js.map +1 -1
- package/out/lib/operations.js +20 -1
- package/out/lib/operations.js.map +1 -1
- package/out/lib/parser.js +12 -16
- package/out/lib/parser.js.map +1 -1
- package/out/lib/ripgrep.js +14 -0
- package/out/lib/ripgrep.js.map +1 -0
- package/out/lib/save-declarations.js +11 -6
- package/out/lib/save-declarations.js.map +1 -1
- package/out/lib/ts-parser-worker.js +39 -0
- package/out/lib/ts-parser-worker.js.map +1 -0
- package/out/lib/ts-parser.js +198 -31
- package/out/lib/ts-parser.js.map +1 -1
- package/package.json +2 -2
- package/src/cli-options.ts +2 -1
- package/src/extractor.ts +8 -1
- package/src/lib/common-properties.ts +71 -5
- package/src/lib/declarations.ts +2 -1
- package/src/lib/event-definition.ts +7 -0
- package/src/lib/events.ts +33 -1
- package/src/lib/object-converter.ts +73 -4
- package/src/lib/operations.ts +20 -2
- package/src/lib/parser.ts +16 -30
- package/src/lib/ripgrep.ts +10 -0
- package/src/lib/save-declarations.ts +15 -9
- package/src/lib/ts-parser-worker.ts +38 -0
- package/src/lib/ts-parser.ts +242 -39
- package/tsconfig.json +1 -0
- 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;
|
package/src/lib/declarations.ts
CHANGED
|
@@ -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) {
|
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();
|
package/src/lib/operations.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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,
|
|
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,
|
|
187
|
-
|
|
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
|
|
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
|
|
15
|
+
import { EventDefinition } from './event-definition';
|
|
16
16
|
|
|
17
17
|
interface EventConflictEntry {
|
|
18
|
-
properties: Record<string,
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
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);
|