@vscode/telemetry-extractor 1.9.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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/SECURITY.md +41 -0
  4. package/documentation/comment-code-annotations.md +197 -0
  5. package/documentation/typescript-code-annotations.md +187 -0
  6. package/documentation/using-the-tool.md +106 -0
  7. package/out/cli-help.js +30 -0
  8. package/out/cli-help.js.map +1 -0
  9. package/out/cli-options.js +52 -0
  10. package/out/cli-options.js.map +1 -0
  11. package/out/extractor.js +83 -0
  12. package/out/extractor.js.map +1 -0
  13. package/out/index.js +10 -0
  14. package/out/index.js.map +1 -0
  15. package/out/lib/common-properties.js +32 -0
  16. package/out/lib/common-properties.js.map +1 -0
  17. package/out/lib/debug-patch.js +17 -0
  18. package/out/lib/debug-patch.js.map +1 -0
  19. package/out/lib/declarations.js +121 -0
  20. package/out/lib/declarations.js.map +1 -0
  21. package/out/lib/events.js +55 -0
  22. package/out/lib/events.js.map +1 -0
  23. package/out/lib/file-writer.js +77 -0
  24. package/out/lib/file-writer.js.map +1 -0
  25. package/out/lib/fragments.js +21 -0
  26. package/out/lib/fragments.js.map +1 -0
  27. package/out/lib/keywords.js +11 -0
  28. package/out/lib/keywords.js.map +1 -0
  29. package/out/lib/logger.js +10 -0
  30. package/out/lib/logger.js.map +1 -0
  31. package/out/lib/object-converter.js +107 -0
  32. package/out/lib/object-converter.js.map +1 -0
  33. package/out/lib/operations.js +118 -0
  34. package/out/lib/operations.js.map +1 -0
  35. package/out/lib/parser.js +191 -0
  36. package/out/lib/parser.js.map +1 -0
  37. package/out/lib/save-declarations.js +94 -0
  38. package/out/lib/save-declarations.js.map +1 -0
  39. package/out/lib/source-spec.js +77 -0
  40. package/out/lib/source-spec.js.map +1 -0
  41. package/out/lib/telemetry-interfaces.js +3 -0
  42. package/out/lib/telemetry-interfaces.js.map +1 -0
  43. package/out/lib/ts-parser.js +219 -0
  44. package/out/lib/ts-parser.js.map +1 -0
  45. package/package.json +39 -0
  46. package/src/cli-help.ts +29 -0
  47. package/src/cli-options.ts +31 -0
  48. package/src/extractor.ts +57 -0
  49. package/src/index.ts +4 -0
  50. package/src/lib/common-properties.ts +41 -0
  51. package/src/lib/debug-patch.ts +13 -0
  52. package/src/lib/declarations.ts +131 -0
  53. package/src/lib/events.ts +56 -0
  54. package/src/lib/file-writer.ts +54 -0
  55. package/src/lib/fragments.ts +24 -0
  56. package/src/lib/keywords.ts +7 -0
  57. package/src/lib/logger.ts +5 -0
  58. package/src/lib/object-converter.ts +85 -0
  59. package/src/lib/operations.ts +89 -0
  60. package/src/lib/parser.ts +184 -0
  61. package/src/lib/save-declarations.ts +71 -0
  62. package/src/lib/source-spec.ts +67 -0
  63. package/src/lib/telemetry-interfaces.ts +40 -0
  64. package/src/lib/ts-parser.ts +206 -0
  65. package/tsconfig.json +67 -0
  66. package/vscode-telemetry-extractor.d.ts +72 -0
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.TsParser = void 0;
23
+ // Copyright (c) Microsoft Corporation.
24
+ // Licensed under the MIT license.
25
+ const ts_morph_1 = require("ts-morph");
26
+ const fs = __importStar(require("fs"));
27
+ const cp = __importStar(require("child_process"));
28
+ const path = __importStar(require("path"));
29
+ const vscode_ripgrep_1 = require("vscode-ripgrep");
30
+ const operations_1 = require("./operations");
31
+ class GDPREvent {
32
+ eventName;
33
+ properties;
34
+ constructor(name) {
35
+ this.eventName = name;
36
+ this.properties = [];
37
+ }
38
+ }
39
+ class NodeVisitor {
40
+ pl_node;
41
+ prop_name;
42
+ inline = false;
43
+ original_prop_name;
44
+ applyEndpoints;
45
+ properties = [];
46
+ resolved_property = Object.create(null);
47
+ constructor(callexpress_node, prop_name, applyEndpoints) {
48
+ this.pl_node = callexpress_node;
49
+ this.prop_name = prop_name;
50
+ this.original_prop_name = prop_name;
51
+ this.applyEndpoints = applyEndpoints;
52
+ }
53
+ visitNode(currentNode, previousNode) {
54
+ let type = currentNode.getTypeAtLocation(this.pl_node);
55
+ // If we mark a property as optional then it is nullable, however we want all properties
56
+ // So we want its non nullable type tl;dr this chops off the | undefined
57
+ if (type.isNullable()) {
58
+ type = type.getNonNullableType();
59
+ }
60
+ if (type.isStringLiteral() || type.isBooleanLiteral()) {
61
+ if (previousNode) {
62
+ // This means it is an inline because we had to recurse deeper than the first level to find the properties
63
+ if (this.prop_name !== previousNode.getEscapedName().toLowerCase() && !this.prop_name.includes(`.${previousNode.getEscapedName().toLowerCase()}`)) {
64
+ this.prop_name = `${this.prop_name}.${previousNode.getEscapedName().toLowerCase()}`;
65
+ this.inline = true;
66
+ }
67
+ }
68
+ // If we don't want endpoints skip them
69
+ if (currentNode.getEscapedName().toLowerCase() === "endpoint" && !this.applyEndpoints)
70
+ return;
71
+ // If it's a string we strip the quotes
72
+ if (type.isStringLiteral()) {
73
+ this.resolved_property[currentNode.getEscapedName()] = type.getText().substring(1, type.getText().length - 1);
74
+ }
75
+ else {
76
+ this.resolved_property[currentNode.getEscapedName()] = type.getText() === 'true';
77
+ }
78
+ return;
79
+ }
80
+ const properties = type.getProperties();
81
+ properties.forEach((prop) => {
82
+ this.visitNode(prop, currentNode);
83
+ });
84
+ // 95% of the time there is only one property in this array but inlines allow
85
+ // for the number of properties found to be unpredictable so we must return an array
86
+ if (this.inline && this.prop_name === this.original_prop_name) {
87
+ // This handles the case where the recursion will cause the inline to be counted one too many times
88
+ return;
89
+ }
90
+ const resolved = Object.create(null);
91
+ if (this.applyEndpoints) {
92
+ this.resolved_property['endPoint'] = this.resolved_property['endPoint'] ? this.resolved_property['endPoint'] : 'none';
93
+ }
94
+ resolved[this.prop_name] = this.resolved_property;
95
+ this.properties.push(resolved);
96
+ this.prop_name = this.original_prop_name;
97
+ }
98
+ resolveProperties(currentNode) {
99
+ this.visitNode(currentNode);
100
+ return this.properties;
101
+ }
102
+ }
103
+ class TsParser {
104
+ sourceDir;
105
+ excludedDirs;
106
+ applyEndpoints;
107
+ lowerCaseEvents;
108
+ project;
109
+ constructor(sourceDir, excludedDirs, applyEndpoints, lowerCaseEvents) {
110
+ this.sourceDir = sourceDir;
111
+ this.excludedDirs = excludedDirs;
112
+ this.applyEndpoints = applyEndpoints;
113
+ this.lowerCaseEvents = lowerCaseEvents;
114
+ // We search for a TS config as that allows the language service to handle weird imports
115
+ if (fs.existsSync(path.join(this.sourceDir, 'src/tsconfig.json'))) {
116
+ this.project = new ts_morph_1.Project({
117
+ tsConfigFilePath: path.join(this.sourceDir, 'src/tsconfig.json'),
118
+ skipAddingFilesFromTsConfig: true
119
+ });
120
+ }
121
+ else if (fs.existsSync(path.join(this.sourceDir, 'tsconfig.json'))) {
122
+ this.project = new ts_morph_1.Project({
123
+ tsConfigFilePath: path.join(this.sourceDir, 'tsconfig.json'),
124
+ skipAddingFilesFromTsConfig: true
125
+ });
126
+ }
127
+ else {
128
+ this.project = new ts_morph_1.Project({});
129
+ }
130
+ const fileGlobs = [];
131
+ fileGlobs.push(`'**/*.ts'`);
132
+ // Excluded added lasts because order determines what takes effect
133
+ this.excludedDirs = (0, operations_1.makeExclusionsRelativeToSource)(this.sourceDir, this.excludedDirs);
134
+ this.excludedDirs.forEach((dir) => {
135
+ fileGlobs.push(`'!${dir}/**'`);
136
+ });
137
+ let rg_glob = '';
138
+ for (const fg of fileGlobs) {
139
+ rg_glob += ` --glob ${fg}`;
140
+ }
141
+ const cmd = `${vscode_ripgrep_1.rgPath} --files-with-matches ${rg_glob} --no-ignore 'publicLog2|publicLogError2' ${this.sourceDir}`;
142
+ try {
143
+ const retrieved_paths = cp.execSync(cmd, { encoding: 'ascii' });
144
+ // Split the paths into an array
145
+ retrieved_paths.split(/(?:\r\n|\r|\n)/g).filter(path => path && path.length > 0).map((f) => {
146
+ this.project.addSourceFileAtPathIfExists(f);
147
+ return f;
148
+ });
149
+ // Empty catch because this fails when there are no typescript annotations which causes weird error messages
150
+ }
151
+ catch { }
152
+ }
153
+ parseFiles() {
154
+ let publicLogUse = [];
155
+ this.project.getSourceFiles().forEach((source) => {
156
+ const descendants = source.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression).filter((c) => c.getExpression().getText().includes('publicLog2'));
157
+ const descendants2 = source.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression).filter((c) => c.getExpression().getText().includes('publicLogError2'));
158
+ publicLogUse = descendants.concat(publicLogUse, descendants2);
159
+ });
160
+ const events = Object.create(null);
161
+ publicLogUse.forEach((pl) => {
162
+ try {
163
+ const typeArgs = pl.getTypeArguments();
164
+ if (typeArgs.length != 2) {
165
+ throw new Error(`Missing generic arguments on public log call ${pl}`);
166
+ }
167
+ if (pl.getArguments()[0].getText() === "eventName") {
168
+ return;
169
+ }
170
+ // Create an event from the name of the first argument passed in
171
+ let event_name = pl.getArguments()[0].getType().isStringLiteral() ? pl.getArguments()[0].getType().getText() : '';
172
+ // If we can't resolve the event_name there is no use continuing
173
+ if (event_name === '') {
174
+ console.error('Unable to resolve event name, skipping....');
175
+ return;
176
+ }
177
+ else {
178
+ event_name = event_name.substring(1, event_name.length - 1);
179
+ }
180
+ event_name = this.lowerCaseEvents ? event_name.toLowerCase() : event_name;
181
+ const created_event = new GDPREvent(event_name);
182
+ // We want the second one because public log is in the form <Event, Classification> and we care about the classification
183
+ const type_properties = typeArgs[1].getType().getProperties();
184
+ type_properties.forEach((prop) => {
185
+ const propName = prop.getEscapedName().toLowerCase();
186
+ const node_visitor = new NodeVisitor(pl, propName, this.applyEndpoints);
187
+ created_event.properties = created_event.properties.concat(node_visitor.resolveProperties(prop));
188
+ });
189
+ // We don't want to overwrite an event if we have already defined it, we just want to add to it
190
+ if (!events[event_name]) {
191
+ events[event_name] = Object.create(null);
192
+ }
193
+ created_event.properties.forEach((prop) => {
194
+ Object.assign(events[event_name], prop);
195
+ });
196
+ }
197
+ catch (err) {
198
+ if (pl.getArguments()[0].getText() === "eventName") {
199
+ return;
200
+ }
201
+ // If the publicLog call isn't generic that means we're just sending an event name with no classifications
202
+ // that are unique to that event (it just has common properties)
203
+ let event_name = pl.getArguments()[0].getType().isStringLiteral() ? pl.getArguments()[0].getType().getText() : '';
204
+ // If we can't resolve the event_name this is most likely because it is not a public log call and therefore we skip it
205
+ if (event_name === '') {
206
+ return;
207
+ }
208
+ else {
209
+ event_name = event_name.substring(1, event_name.length - 1);
210
+ }
211
+ event_name = this.lowerCaseEvents ? event_name.toLowerCase() : event_name;
212
+ events[event_name] = {};
213
+ }
214
+ });
215
+ return events;
216
+ }
217
+ }
218
+ exports.TsParser = TsParser;
219
+ //# sourceMappingURL=ts-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ts-parser.js","sourceRoot":"","sources":["../../src/lib/ts-parser.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@vscode/telemetry-extractor",
3
+ "version": "1.9.5",
4
+ "description": "Extracts telemetry from VS Code",
5
+ "main": "out/index.js",
6
+ "typings": "vscode-telemetry-extractor.d.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/microsoft/vscode-telemetry-extractor"
10
+ },
11
+ "scripts": {
12
+ "test": "mocha out/tests/mocha/*.js",
13
+ "coverage": "nyc mocha out/tests/mocha/*.js",
14
+ "extract-core": "node ./out/extractor.js --sourceDir src/telemetry-sources/vscode --excludedDir src/telemetry-sources/vscode/extensions --eventPrefix monacoworkbench/ --outputDir . --applyEndpoints",
15
+ "extract-core-verbose": "node ./out/extractor.js --sourceDir src/telemetry-sources/vscode --excludedDir src/telemetry-sources/vscode/extensions --eventPrefix monacoworkbench/ --outputDir . --applyEndpoints --silent --verbose",
16
+ "compile": "tsc",
17
+ "watch": "tsc --watch"
18
+ },
19
+ "bin": {
20
+ "vscode-telemetry-extractor": "./out/extractor.js"
21
+ },
22
+ "author": "lramos15",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "command-line-args": "^5.2.0",
26
+ "ts-morph": "^12.2.0",
27
+ "vscode-ripgrep": "^1.12.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/command-line-args": "^5.2.0",
31
+ "@types/mocha": "^9.0.0",
32
+ "@types/node": "^15.12.2",
33
+ "mocha": "^9.1.3",
34
+ "nyc": "^15.1.0",
35
+ "source-map-support": "^0.5.20",
36
+ "ts-node": "^10.4.0",
37
+ "typescript": "^4.4.4"
38
+ }
39
+ }
@@ -0,0 +1,29 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { optionDefinitions } from './cli-options';
4
+
5
+ function buildHelpMessage() {
6
+ console.log('Allows the extraction of telemetry annotation from code. For more details please read: https://github.com/microsoft/vscode-telemetry-extractor/blob/master/README.md\n');
7
+ const optionLengths = optionDefinitions.map((opt) => {
8
+ if (opt.description) {
9
+ return opt.name.length
10
+ }
11
+ return -1;
12
+ });
13
+ const maxLength = Math.max(...optionLengths);
14
+ for (const opt of optionDefinitions) {
15
+ if (opt.description) {
16
+ let outputString = '';
17
+ if (opt.alias) {
18
+ outputString += `-${opt.alias} `;
19
+ }
20
+ // Extra padding if you don't have an alias
21
+ const extraPadding = opt.alias ? 0 : 3;
22
+ outputString += `--${opt.name.padEnd(maxLength + extraPadding)}\t\t\t`;
23
+ outputString += `${opt.description}`;
24
+ console.log(outputString);
25
+ }
26
+ }
27
+ }
28
+
29
+ buildHelpMessage();
@@ -0,0 +1,31 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import commandLineArgs from 'command-line-args';
4
+ import * as path from 'path';
5
+
6
+ function resolveDirectories(options: commandLineArgs.CommandLineOptions): commandLineArgs.CommandLineOptions {
7
+ const cwd = process.cwd();
8
+
9
+ if (options.sourceDir) {
10
+ options.sourceDir = (options.sourceDir as string[]).map(d => path.resolve(cwd, d));
11
+ }
12
+ if (options.outputDir) {
13
+ options.outputDir = path.resolve(cwd, options.outputDir);
14
+ }
15
+
16
+ return options;
17
+ }
18
+
19
+ export const optionDefinitions = [
20
+ { name: 'sourceDir', alias: 's', description: 'The folder which you want to extract telemetry from (relative to the CWD)', type: String, multiple: true },
21
+ { name: 'excludedDir', alias: 'x', description: 'A subdirectory which you would like to exclude from the extraction (relative to the CWD)', type: String, multiple: true, defaultValue: [] },
22
+ { name: 'config', alias: 'c', description: 'A JSON Configuration file containing extraction details', type: String },
23
+ { name: 'outputDir', alias: 'o', description: 'The directory which you would like the outputted JSON file to be placed in', type: String },
24
+ { name: 'eventPrefix', alias: 'p', type: String, description: 'The string you wish to prepend to every telemetry event.', defaultValue: '' },
25
+ { name: 'help', alias: 'h', type: Boolean, description: 'Displays the help dialog which provides more information on how to use the tool', defaultValue: false },
26
+ { name: 'applyEndpoints', alias: 'e', type: Boolean, defaultValue: false },
27
+ { name: 'silenceOutput', type: Boolean, description: 'Silences all progress messages.', defaultValue: false },
28
+ { name: 'verbose', alias: 'v', type: Boolean, defaultValue: false }
29
+ ];
30
+
31
+ export const options = resolveDirectories(commandLineArgs(optionDefinitions, { partial: true }));
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) Microsoft Corporation.
3
+ // Licensed under the MIT license.
4
+ import { options } from './cli-options';
5
+ import * as path from 'path';
6
+ import { cwd } from 'process';
7
+ import { writeToFile, extractAndResolveDeclarations } from './lib/save-declarations';
8
+ import { convertConfigToSourceSpecs, ParserOptions, SourceSpec } from './lib/source-spec';
9
+ import { logMessage } from './lib/logger';
10
+
11
+ if (options.config) {
12
+ const sourceSpecs = convertConfigToSourceSpecs(options.config);
13
+ extractAndResolveDeclarations(sourceSpecs).then((declarations) => {
14
+ if (options.outputDir) {
15
+ writeToFile(options.outputDir, declarations, 'config-resolved', !options.silent);
16
+ } else {
17
+ console.log(JSON.stringify(declarations));
18
+ }
19
+ });
20
+ } else if (options.help || !options.sourceDir) {
21
+ require('./cli-help');
22
+ } else {
23
+ const parserOptions: ParserOptions = {
24
+ eventPrefix: options.eventPrefix,
25
+ applyEndpoints: options.applyEndpoints,
26
+ patchDebugEvents: false,
27
+ lowerCaseEvents: false,
28
+ silenceOutput: options.silenceOutput,
29
+ verbose: options.verbose
30
+ };
31
+ logMessage('....running.', parserOptions.silenceOutput);
32
+ const sourceSpec: SourceSpec = {
33
+ sourceDirs: resolveDirectories(options.sourceDir),
34
+ excludedDirs: options.excludedDir ? resolveDirectories(options.excludedDir) : [],
35
+ parserOptions: parserOptions
36
+ };
37
+ extractAndResolveDeclarations([sourceSpec]).then((declarations) => {
38
+ if (options.outputDir) {
39
+ writeToFile(options.outputDir, declarations, 'declarations-resolved', !options.silent);
40
+ } else {
41
+ console.log(JSON.stringify(declarations));
42
+ }
43
+ });
44
+ }
45
+
46
+ // Resolves an array of paths
47
+ function resolveDirectories(dirs: string[], workingDir?: string): string[] {
48
+ if (workingDir) {
49
+ if (path.isAbsolute(workingDir)) {
50
+ return dirs.map(s => path.resolve(workingDir, s));
51
+ } else {
52
+ return dirs.map(s => path.resolve(cwd(), workingDir, s));
53
+ }
54
+ } else {
55
+ return dirs.map(s => path.resolve(cwd(), s));
56
+ }
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ export { extractAndResolveDeclarations } from './lib/save-declarations';
4
+ export { convertConfigToSourceSpecs, SourceSpec } from './lib/source-spec';
@@ -0,0 +1,41 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { IProperty } from "./telemetry-interfaces";
4
+
5
+ export class CommonProperties {
6
+ public properties: Array<Property>;
7
+ constructor () {
8
+ this.properties = [];
9
+ }
10
+ }
11
+
12
+ export class Property implements IProperty {
13
+ public name: string;
14
+ public classification: string;
15
+ public purpose: string;
16
+ public endPoint?: string;
17
+ public isMeasurement?: boolean;
18
+ public expiration?: string;
19
+ public owner?: string;
20
+ public comment?: string;
21
+
22
+ constructor (
23
+ name: string,
24
+ classification: string,
25
+ purpose: string,
26
+ expiration?: string,
27
+ owner?: string,
28
+ comment?: string,
29
+ endpoint?: string,
30
+ isMeasurement?: boolean
31
+ ) {
32
+ this.name = name;
33
+ this.classification = classification;
34
+ this.purpose = purpose;
35
+ this.endPoint = endpoint;
36
+ this.isMeasurement = isMeasurement;
37
+ this.expiration = expiration;
38
+ this.owner = owner;
39
+ this.comment = comment;
40
+ }
41
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { Events, Event } from "./events";
4
+ import { Property } from "./common-properties";
5
+
6
+ export function patchDebugEvents(currentEvents: Events, eventPrefix: string) {
7
+ const debugProtocolErrorResponse = new Event(eventPrefix + 'debugProtocolErrorResponse');
8
+ debugProtocolErrorResponse.properties.push(new Property('error', 'CallstackOrException', 'PerformanceAndHealth'));
9
+ const optin = new Event(eventPrefix + 'optinstatus');
10
+ optin.properties.push(new Property('optin', 'SystemMetaData', 'BusinessInsight', undefined, undefined, undefined, undefined, true));
11
+ currentEvents.dataPoints.push(debugProtocolErrorResponse);
12
+ currentEvents.dataPoints.push(optin);
13
+ }
@@ -0,0 +1,131 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { Fragments } from "./fragments";
4
+ import { Events, Include, Inline } from "./events";
5
+ import { CommonProperties, Property } from "./common-properties";
6
+
7
+ export interface Declarations {
8
+ fragments: Fragments;
9
+ events: Events;
10
+ commonProperties: CommonProperties;
11
+ }
12
+
13
+ // We don't output Fragments as they don't really provide much context on their own
14
+ export interface OutputtedDeclarations {
15
+ events: Events;
16
+ commonProperties: CommonProperties;
17
+ }
18
+
19
+ function resolveIncludes(target: Events | Fragments, fragments: Fragments) {
20
+ const notResolved: Array<String> = [];
21
+ const usedFragments: Array<String> = [];
22
+ for (const item of target.dataPoints) {
23
+ for (let i = 0; i < item.properties.length; i++) {
24
+ const property = item.properties[i];
25
+ if (property instanceof Include) {
26
+ // Removes the current include from the array since we have filled it in
27
+ item.properties = item.properties.filter((val) => {
28
+ return val != property;
29
+ });
30
+ for (let reference of property.includeNames) {
31
+ reference = reference.substring(2, reference.length - 1);
32
+ const fragment = fragments.dataPoints.find((f) => {
33
+ return f.name === reference;
34
+ });
35
+ if (fragment) {
36
+ item.properties = item.properties.concat(fragment.properties);
37
+ usedFragments.push(fragment.name);
38
+ } else {
39
+ notResolved.push(reference);
40
+ }
41
+ // This is an inefficient solution, but since adding and removing from the array
42
+ // messes up the iteration we just restart it until we can get through the whole loop without any includes
43
+ i = 0;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return { target, notResolved, usedFragments };
49
+ }
50
+
51
+ function resolveInlines(target: Events | Fragments, fragments: Fragments) {
52
+ const notResolved: Array<String> = [];
53
+ const usedFragments: Array<String> = [];
54
+ for (const item of target.dataPoints) {
55
+ for (let i = 0; i < item.properties.length; i++) {
56
+ const property = item.properties[i];
57
+ if (property instanceof Inline) {
58
+ // Removes the current inline from the array since we are resolving it
59
+ item.properties = item.properties.filter((val) => {
60
+ return val !== property;
61
+ });
62
+ for (let reference of property.inlines) {
63
+ // Gets rid of the ${}
64
+ reference = reference.substring(2, reference.length - 1);
65
+ const fragment = fragments.dataPoints.find((f) => {
66
+ return f.name === reference;
67
+ });
68
+ if (fragment) {
69
+ usedFragments.push(fragment.name);
70
+ fragment.properties.forEach((prop) => {
71
+ if (prop instanceof Property) {
72
+ // We create the new property in the format inlineName.propName keeping the rest the same
73
+ let currentProp = new Property(`${property.inlineName}.${prop.name}`, prop.classification, prop.purpose, prop.expiration, prop.owner, prop.comment);
74
+ if (prop.endPoint) {
75
+ currentProp.endPoint = prop.endPoint;
76
+ }
77
+ if (prop.isMeasurement) {
78
+ currentProp.isMeasurement = prop.isMeasurement;
79
+ }
80
+ item.properties.push(currentProp);
81
+ }
82
+ });
83
+ } else {
84
+ notResolved.push(reference);
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return { target, notResolved, usedFragments };
91
+ }
92
+
93
+ export function resolveDeclarations(declarations: Declarations, verbose: boolean) {
94
+ let notResolved: Array<String> = [];
95
+ let usedFragments: Array<String> = [];
96
+ if (verbose) {
97
+ const fragmentsResolveInlines = resolveInlines(declarations.fragments, declarations.fragments);
98
+ const eventsResolveInlines = resolveInlines(declarations.events, declarations.fragments);
99
+ const fragmentsResolveIncludes = resolveIncludes(declarations.fragments, declarations.fragments);
100
+ const eventsResolveIncludes = resolveIncludes(declarations.events, declarations.fragments);
101
+ notResolved = notResolved.concat(fragmentsResolveIncludes.notResolved);
102
+ notResolved = notResolved.concat(fragmentsResolveInlines.notResolved);
103
+ notResolved = notResolved.concat(eventsResolveIncludes.notResolved);
104
+ notResolved = notResolved.concat(eventsResolveInlines.notResolved);
105
+ // Remove duplicates
106
+ notResolved = [...new Set(notResolved)];
107
+ usedFragments = usedFragments.concat(fragmentsResolveIncludes.usedFragments);
108
+ usedFragments = usedFragments.concat(fragmentsResolveInlines.usedFragments);
109
+ usedFragments = usedFragments.concat(eventsResolveIncludes.usedFragments);
110
+ usedFragments = usedFragments.concat(eventsResolveInlines.usedFragments);
111
+ // Remove duplicates
112
+ const usedFragmentsSet = new Set(usedFragments);
113
+ const allFragments = declarations.fragments.dataPoints.map(f => f.name);
114
+ // The Symmetric difference of a set
115
+ let unusedFragments = [...new Set(allFragments.filter(x => !usedFragmentsSet.has(x)))];
116
+ notResolved = notResolved.length > 0 ? notResolved : ['None'];
117
+ unusedFragments = unusedFragments.length > 0 ? unusedFragments : ['None'];
118
+ console.log(`Unresolved References: ${notResolved}`);
119
+ console.log(`Unused Fragments: ${unusedFragments}`);
120
+ declarations.fragments = fragmentsResolveInlines.target;
121
+ declarations.events = eventsResolveInlines.target;
122
+ declarations.fragments = fragmentsResolveIncludes.target;
123
+ declarations.events = eventsResolveIncludes.target;
124
+ } else {
125
+ declarations.fragments = resolveInlines(declarations.fragments, declarations.fragments).target;
126
+ declarations.events = resolveInlines(declarations.events, declarations.fragments).target;
127
+ declarations.fragments = resolveIncludes(declarations.fragments, declarations.fragments).target;
128
+ declarations.events = resolveIncludes(declarations.events, declarations.fragments).target;
129
+ }
130
+ return { events: declarations.events, commonProperties: declarations.commonProperties, fragments: declarations.fragments };
131
+ }
@@ -0,0 +1,56 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { IProperty, IInclude, ITelemetryDataPoint, ITelemetryData, IInline, IWildcard, IWildcardEntry } from './telemetry-interfaces';
4
+ import { Property } from './common-properties';
5
+
6
+
7
+ export class Events implements ITelemetryData {
8
+ public dataPoints: Array<Event>;
9
+ constructor () {
10
+ this.dataPoints = [];
11
+ }
12
+ }
13
+
14
+ export class Event implements ITelemetryDataPoint {
15
+ public name: string;
16
+ // It gets a little more complicated here as events can have a bunch of different things
17
+ public properties: Array<Property | Include | Inline | Wildcard>;
18
+ constructor (name: string) {
19
+ this.name = name;
20
+ this.properties = [];
21
+ }
22
+ }
23
+
24
+ export class Include implements IInclude {
25
+ public includeNames: Array<string>;
26
+ constructor (includeNames: Array<string>) {
27
+ this.includeNames = includeNames;
28
+ }
29
+ }
30
+
31
+ export class Inline implements IInline {
32
+ public inlineName: string;
33
+ public inlines: Array<string>;
34
+ constructor (name: string, inlines: Array<string>) {
35
+ this.inlineName = name;
36
+ this.inlines = inlines;
37
+ }
38
+ }
39
+
40
+ export class Wildcard implements IWildcard {
41
+ public entries: Array<WildcardEntry>;
42
+ constructor () {
43
+ this.entries = [];
44
+ }
45
+ }
46
+
47
+ export class WildcardEntry implements IWildcardEntry {
48
+ public prefix: string;
49
+ public classification: {classification: string, purpose: string};
50
+ public endpoint: string | undefined;
51
+ constructor (prefix: string, classification: {classification: string, purpose: string}, endPoint?: string | undefined) {
52
+ this.prefix = prefix;
53
+ this.classification = classification;
54
+ this.endpoint = endPoint;
55
+ }
56
+ }