@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,54 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ /**
7
+ * @param {string} outputFile
8
+ * @param {string} contents
9
+ * @returns {Promise}
10
+ */
11
+ export async function writeFile(outputFile: string, contents: string) {
12
+ const directory = path.dirname(outputFile);
13
+ return mkdirp(directory).then((dir: string) => {
14
+ return new Promise<void>((resolve, reject) => {
15
+ fs.writeFile(outputFile, contents, { encoding: 'utf8' }, (err: any) => {
16
+ if (err) {
17
+ reject(err);
18
+ } else {
19
+ resolve();
20
+ }
21
+ })
22
+ });
23
+ });
24
+ }
25
+
26
+ // copied from https://github.com/Microsoft/vscode/blob/master/src/main.js#L139
27
+ async function mkdirp(dir: string): Promise<any> {
28
+ return mkdir(dir)
29
+ .then(null, function (err: any) {
30
+ if (err && err.code === 'ENOENT') {
31
+ var parent = path.dirname(dir);
32
+ if (parent !== dir) { // if not arrived at root
33
+ return mkdirp(parent)
34
+ .then(function () {
35
+ return mkdir(dir);
36
+ });
37
+ }
38
+ }
39
+ throw err;
40
+ });
41
+ }
42
+
43
+ // copied from https://github.com/Microsoft/vscode/blob/master/src/main.js#L155
44
+ async function mkdir(dir: string): Promise<any> {
45
+ return new Promise(function (resolve, reject) {
46
+ fs.mkdir(dir, function (err: any) {
47
+ if (err && err.code !== 'EEXIST') {
48
+ reject(err);
49
+ } else {
50
+ resolve(dir);
51
+ }
52
+ });
53
+ });
54
+ }
@@ -0,0 +1,24 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { ITelemetryData, ITelemetryDataPoint, IProperty, IWildcard } from './telemetry-interfaces';
4
+ import { Property } from './common-properties';
5
+ import { Include, Inline } from './events';
6
+
7
+
8
+ // Fragments are retrieved as an object of objects of objects. So this just makes it easier to see the structure.
9
+ export class Fragments implements ITelemetryData{
10
+ public dataPoints: Array<Fragment>;
11
+ constructor () {
12
+ this.dataPoints = [];
13
+ }
14
+ }
15
+
16
+ export class Fragment implements ITelemetryDataPoint {
17
+ public name: string;
18
+ public properties: Array<Property | Include | Inline | IWildcard>;
19
+ constructor (name: string) {
20
+ this.name = name;
21
+ this.properties = [];
22
+
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ export const wildcard = '${wildcard}';
4
+ export const inline = '${inline}';
5
+ export const include = '${include}';
6
+ export const prefix = '${prefix}';
7
+ export const classification = '${classification}';
@@ -0,0 +1,5 @@
1
+ export function logMessage(msg: string, silenceOutput = false) {
2
+ if (!silenceOutput) {
3
+ console.log(msg);
4
+ }
5
+ }
@@ -0,0 +1,85 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { OutputtedDeclarations } from "./declarations";
4
+ import { Property } from "./common-properties";
5
+ import { Wildcard } from "./events";
6
+ import * as keywords from './keywords';
7
+
8
+ // Converts the declarations array to an object format for easy readability.
9
+
10
+ export async function transformOutput(output: OutputtedDeclarations): Promise<OutputtedDeclarations> {
11
+ const newEvents = Object.create(null);
12
+ const oldEvents = output.events.dataPoints;
13
+ for (const event of oldEvents) {
14
+ newEvents[event.name] = Object.create(null);
15
+ //We know there won't be anymore includes or inlines because we have resolved them
16
+ for (const property of event.properties as Array<Property | Wildcard>) {
17
+ if (property instanceof Wildcard) {
18
+ newEvents[event.name][keywords.wildcard] = newEvents[event.name][keywords.wildcard] ? newEvents[event.name][keywords.wildcard] : [];
19
+ for (const entry of property.entries) {
20
+ const found = newEvents[event.name][keywords.wildcard].find((e: any) => {
21
+ return e[keywords.prefix] === entry.prefix.toLowerCase();
22
+ });
23
+ if (found) continue;
24
+ const newEntry = Object.create(null);
25
+ newEntry[keywords.prefix] = entry.prefix.toLowerCase();
26
+ if (entry.endpoint) {
27
+ newEntry[keywords.classification] = { classification: entry.classification.classification, purpose: entry.classification.purpose, endPoint: "none" };
28
+ } else {
29
+ newEntry[keywords.classification] = entry.classification;
30
+ }
31
+ newEvents[event.name][keywords.wildcard].push(newEntry);
32
+ }
33
+ } else {
34
+ // Handles the case where the comments can be inconsistent
35
+ // We want to ensure that if isMeasurement is ever flagged it gets propogated
36
+ if (newEvents[event.name][propetyNameChanger(property.name)]) {
37
+ if (property.isMeasurement) newEvents[event.name][propetyNameChanger(property.name)]['isMeasurement'] = property.isMeasurement;
38
+ continue;
39
+ }
40
+ newEvents[event.name][propetyNameChanger(property.name)] = { classification: property.classification, purpose: property.purpose };
41
+ if (property.expiration) {
42
+ newEvents[event.name][propetyNameChanger(property.name)]['expiration'] = property.expiration;
43
+ }
44
+ if (property.owner) {
45
+ newEvents[event.name][propetyNameChanger(property.name)]['owner'] = property.owner;
46
+ }
47
+ if (property.comment) {
48
+ newEvents[event.name][propetyNameChanger(property.name)]['comment'] = property.comment;
49
+ }
50
+ if (property.endPoint) {
51
+ newEvents[event.name][propetyNameChanger(property.name)]['endPoint'] = property.endPoint;
52
+ }
53
+ if (property.isMeasurement) {
54
+ newEvents[event.name][propetyNameChanger(property.name)]['isMeasurement'] = property.isMeasurement;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ const newCommonProperties = Object.create(null);
60
+ const oldCommonProperties = output.commonProperties.properties;
61
+ for (const property of oldCommonProperties) {
62
+ // Handles the case where the comments can be incosistent
63
+ // We want to ensure that if isMeasurement is ever flagged it gets propogated
64
+ if (newCommonProperties[propetyNameChanger(property.name)]) {
65
+ if (property.isMeasurement) newCommonProperties[propetyNameChanger(property.name)]['isMeasurement'] = property.isMeasurement;
66
+ continue;
67
+ }
68
+ newCommonProperties[propetyNameChanger(property.name)] = { classification: property.classification, purpose: property.purpose };
69
+ if (property.endPoint) {
70
+ newCommonProperties[propetyNameChanger(property.name)]['endPoint'] = property.endPoint;
71
+ }
72
+ if (property.isMeasurement) {
73
+ newCommonProperties[propetyNameChanger(property.name)]['isMeasurement'] = property.isMeasurement;
74
+ }
75
+ }
76
+ return { events: newEvents, commonProperties: newCommonProperties };
77
+ }
78
+
79
+ function propetyNameChanger(name: string) {
80
+ name = name.toLowerCase();
81
+ if (name.includes('<number>')) {
82
+ name = name.replace('<number>', '<NUMBER>');
83
+ }
84
+ return name;
85
+ }
@@ -0,0 +1,89 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { Fragments, Fragment } from "./fragments";
4
+ import { Events, Event, Include, Inline, Wildcard, WildcardEntry } from "./events";
5
+ import { Property } from "./common-properties";
6
+ import * as keywords from './keywords';
7
+
8
+ export function merge(target: Fragments | Events, source: Fragments | Events) {
9
+ for (const item of source.dataPoints) {
10
+ const found = target.dataPoints.find((f) => {
11
+ return f.name == item.name;
12
+ });
13
+ // We combine their properties together if the event already exists
14
+ if (found) {
15
+ found.properties = found.properties.concat(item.properties);
16
+ } else {
17
+ target.dataPoints.push(item);
18
+ }
19
+ }
20
+ }
21
+
22
+ // Searches the object for an event or fragment of the specific name
23
+ // If found returns, if not found creates it, places it in the array, and then returns
24
+ export function findOrCreate(searchTarget: Events | Fragments, name: string) {
25
+ let found = searchTarget.dataPoints.find((item) => {
26
+ return item.name === name;
27
+ });
28
+ if (!found) {
29
+ if (searchTarget instanceof Events) {
30
+ found = new Event(name);
31
+ } else {
32
+ found = new Fragment(name);
33
+ }
34
+ }
35
+ searchTarget.dataPoints.push(found);
36
+ return found;
37
+ }
38
+
39
+ export function mergeWildcards(wildcard: any[], target: Event | Fragment, applyEndpoints: boolean) {
40
+ let wildCard = target.properties.find((item) => {
41
+ return item instanceof Wildcard;
42
+ }) as Wildcard;
43
+ // if we don't have a wildcard yet we make one
44
+ if (!wildCard) {
45
+ wildCard = new Wildcard();
46
+ target.properties.push(wildCard);
47
+ }
48
+ wildcard.forEach(w => {
49
+ if (applyEndpoints) {
50
+ wildCard.entries.push(new WildcardEntry(w[keywords.prefix], w[keywords.classification], 'none'));
51
+ } else {
52
+ wildCard.entries.push(new WildcardEntry(w[keywords.prefix], w[keywords.classification]));
53
+ }
54
+ });
55
+ }
56
+
57
+ export function populateProperties(properties: any, target: Event | Fragment, applyEndpoints = false) {
58
+ for (const propertyName in properties) {
59
+ const currentProperty = properties[propertyName];
60
+ if (propertyName === keywords.include) {
61
+ target.properties.push(new Include(currentProperty));
62
+ } else if (currentProperty[keywords.inline]) {
63
+ // We consider the property name the inline name so when we resolve we can do prop.Inline for the new properties
64
+ target.properties.push(new Inline(propertyName, currentProperty[keywords.inline]));
65
+ } else if (propertyName === keywords.wildcard) {
66
+ mergeWildcards(currentProperty, target, applyEndpoints);
67
+ } else {
68
+ const prop = new Property(propertyName, currentProperty.classification, currentProperty.purpose, currentProperty.expiration, currentProperty.owner, currentProperty.comment);
69
+ if (applyEndpoints) {
70
+ const endpoint = currentProperty.endpoint ? currentProperty.endpoint : 'none';
71
+ prop.endPoint = endpoint;
72
+ }
73
+ if (currentProperty.isMeasurement) {
74
+ prop.isMeasurement = currentProperty.isMeasurement;
75
+ }
76
+ target.properties.push(prop);
77
+ }
78
+ }
79
+ }
80
+
81
+ export function makeExclusionsRelativeToSource(sourceDir: string, excludedDirs: string[]) {
82
+ const relativeExclusions = [];
83
+ for (const excluded of excludedDirs) {
84
+ if (excluded.includes(sourceDir)) {
85
+ relativeExclusions.push(excluded.replace(sourceDir, ''));
86
+ }
87
+ }
88
+ return relativeExclusions;
89
+ }
@@ -0,0 +1,184 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { rgPath } from 'vscode-ripgrep';
4
+ import * as path from 'path';
5
+ import * as cp from 'child_process';
6
+ import * as fs from 'fs';
7
+ import { Fragments } from './fragments';
8
+ import { Property, CommonProperties } from './common-properties';
9
+ import { Events } from './events';
10
+ import { Declarations } from './declarations';
11
+ import { merge, findOrCreate, populateProperties, makeExclusionsRelativeToSource } from './operations';
12
+
13
+ export class Parser {
14
+
15
+ private sourceDirs: string[];
16
+ private excludedDirs: string[];
17
+ private applyEndpoints: boolean;
18
+
19
+ private lowerCaseEvents: boolean;
20
+
21
+ constructor(sourceDirs: string[], excludedDirs: string[], applyEndpoints: boolean, lowerCaseEvents: boolean) {
22
+ this.sourceDirs = sourceDirs;
23
+ this.excludedDirs = excludedDirs;
24
+ this.applyEndpoints = applyEndpoints;
25
+ this.lowerCaseEvents = lowerCaseEvents;
26
+ }
27
+
28
+ private toRipGrepOption(dir: string) {
29
+ while (dir.startsWith('/')) {
30
+ dir = dir.substr(1);
31
+ }
32
+ return `--glob "!${dir}/**" `;
33
+ }
34
+
35
+ private extractComments(absoluteFilePaths: string[], commentMatcher: RegExp, collector: Function) {
36
+ absoluteFilePaths.forEach(absoluteFilePath => {
37
+ if (absoluteFilePath) {
38
+ const fileContents = fs.readFileSync(absoluteFilePath);
39
+ let match;
40
+ while (match = commentMatcher.exec(fileContents.toString())) {
41
+ collector(absoluteFilePath, match);
42
+ }
43
+ }
44
+ });
45
+
46
+ }
47
+
48
+ // Converts relative paths to absolute utilizing the CWD
49
+ private asAbsoluteFilePaths(relativeFilePaths: string[]) {
50
+ return relativeFilePaths.map(r => path.resolve(r));
51
+ }
52
+
53
+ // Finds all files containing common telemetry properties in the given directory
54
+ private findFilesWithCommonProperties(sourceDir: string) {
55
+ const ripgrepPattern = '//\\s*__GDPR__COMMON__';
56
+ return this.findFiles(ripgrepPattern, sourceDir);
57
+ }
58
+
59
+ private findCommonProperties(sourceDir: string) {
60
+ const filesWithCommonProperties = this.asAbsoluteFilePaths(this.findFilesWithCommonProperties(sourceDir));
61
+
62
+ const commonPropertyMatcher = /\/\/\s*__GDPR__COMMON__(.*)$/mg;
63
+ const commonPropertyDeclarations = new CommonProperties();
64
+ this.extractComments(filesWithCommonProperties, commonPropertyMatcher, (filePath: string, match: Array<string>) => {
65
+ try {
66
+ const commonPropertyDeclaration = JSON.parse(`{ ${match[1]} }`);
67
+ const propertyName = Object.keys(commonPropertyDeclaration)[0];
68
+ const properties = commonPropertyDeclaration[propertyName];
69
+ // Add all the common properties to the common property object
70
+ const prop = new Property(propertyName, properties.classification, properties.purpose);
71
+ if (this.applyEndpoints) {
72
+ const endpoint = properties.endPoint ? properties.endPoint : 'none';
73
+ prop.endPoint = endpoint;
74
+ }
75
+ if (properties.isMeasurement) {
76
+ prop.isMeasurement = properties.isMeasurement;
77
+ }
78
+ commonPropertyDeclarations.properties.push(prop);
79
+ } catch (error) {
80
+ console.error(`Common Property Declaration Error: ${error} in file ${filePath}`);
81
+ console.error(`Source comment:\n${match[0]}`);
82
+ }
83
+ });
84
+ return commonPropertyDeclarations;
85
+ }
86
+
87
+ // Finds all files containing event fragments in the given directory
88
+ private findFilesWithFragments(sourceDir: string) {
89
+ const ripgrepPattern = '/\*\\s*__GDPR__FRAGMENT__';
90
+ return this.findFiles(ripgrepPattern, sourceDir);
91
+ }
92
+
93
+ ///
94
+ private findFragments(sourceDir: string) {
95
+ const filesWithFragments = this.asAbsoluteFilePaths(this.findFilesWithFragments(sourceDir));
96
+
97
+ // Using [\s\S]* instead of .* since the latter does not match when using /m option
98
+ const fragmentMatcher = /\/\*\s*__GDPR__FRAGMENT__([\s\S]*?)\*\//mg;
99
+ const fragmentDeclarations = new Fragments();
100
+ this.extractComments(filesWithFragments, fragmentMatcher, (filePath: string, match: Array<string>) => {
101
+ try {
102
+ const fragmentDeclaration = JSON.parse(`{ ${match[1]} }`);
103
+ // There's only ever one key per match
104
+ const fragmentName = Object.keys(fragmentDeclaration)[0];
105
+ // Checks to see if we have a fragment of the given name, else creates.
106
+ const fragment = findOrCreate(fragmentDeclarations, fragmentName);
107
+ const fragmentProperties = fragmentDeclaration[fragmentName];
108
+ populateProperties(fragmentProperties, fragment, this.applyEndpoints);
109
+ } catch (error) {
110
+ console.error(`Fragment Declaration Error: ${error} in file ${filePath}`);
111
+ console.error(`Source comment:\n${match[0]}`);
112
+ }
113
+ });
114
+ return fragmentDeclarations;
115
+ }
116
+
117
+ // Find all files with complete events
118
+ private findFilesWithEvents(sourceDir: string) {
119
+ const ripgrepPattern = '/\*\\s*__GDPR__\\b';
120
+ return this.findFiles(ripgrepPattern, sourceDir);
121
+ }
122
+
123
+ private findEvents(sourceDir: string) {
124
+ const filesWithEvents = this.asAbsoluteFilePaths(this.findFilesWithEvents(sourceDir));
125
+
126
+ // Using [\s\S]* instead of .* since the latter does not match when using /m option
127
+ const eventMatcher = /\/\*\s*__GDPR__\b([\s\S]*?)\*\//mg;
128
+ const eventDeclarations = new Events();
129
+ this.extractComments(filesWithEvents, eventMatcher, (filePath: string, match: Array<string>) => {
130
+ try {
131
+ const eventDeclaration = JSON.parse(`{ ${match[1]} }`);
132
+ let eventName = Object.keys(eventDeclaration)[0];
133
+ eventName = this.lowerCaseEvents ? eventName.toLowerCase() : eventName;
134
+ const event = findOrCreate(eventDeclarations, eventName);
135
+ // Get the propeties which the event possesses
136
+ const eventProperties = eventDeclaration[Object.keys(eventDeclaration)[0]];
137
+ populateProperties(eventProperties, event, this.applyEndpoints);
138
+ } catch (error) {
139
+ console.error(`Event Declaration Error: ${error} in file ${filePath}`);
140
+ console.error(`Source comment:\n${match[0]}`);
141
+ }
142
+ });
143
+ return eventDeclarations;
144
+ }
145
+
146
+ // Utilizes a regex to find the files containing the specific pattern
147
+ private findFiles(ripgrepPattern: string, sourceDir: string) {
148
+ const relativeExclusions = makeExclusionsRelativeToSource(sourceDir, this.excludedDirs);
149
+ const exclusions = relativeExclusions.length === 0 || relativeExclusions[0] === '' ? '' : relativeExclusions.map(this.toRipGrepOption).join('');
150
+ const cmd = `${rgPath} --files-with-matches --glob "*.ts" ${exclusions} --regexp "${ripgrepPattern}" -- ${sourceDir}`;
151
+ try {
152
+ let filePaths = cp.execSync(cmd, { encoding: 'ascii', cwd: `${sourceDir}` });
153
+ return filePaths.split(/(?:\r\n|\r|\n)/g).filter(path => path && path.length > 0);
154
+ } catch (err) {
155
+ // ripgrep's return code != 0 if there are no matches
156
+ return [];
157
+ }
158
+ }
159
+
160
+ private async parse(sourceDir: string): Promise<Declarations> {
161
+ const fragments = this.findFragments(sourceDir);
162
+ const events = this.findEvents(sourceDir);
163
+ const commonProperties = this.findCommonProperties(sourceDir);
164
+ return { fragments: fragments, events: events, commonProperties: commonProperties };
165
+ }
166
+
167
+ public extractDeclarations(): Promise<Declarations> {
168
+ return new Promise((resolve, reject) => {
169
+ // Find all the properties for all files
170
+ const promises = this.sourceDirs.map(sd => this.parse(sd));
171
+ // Now we must merge them into one superset of all declarations
172
+ Promise.all(promises).then(parseResult => {
173
+ const declarations = { fragments: new Fragments(), events: new Events(), commonProperties: new CommonProperties() };
174
+ for (const currentResult of parseResult) {
175
+ merge(declarations.fragments, currentResult.fragments);
176
+ merge(declarations.events, currentResult.events);
177
+ // We just concatenate common properties
178
+ declarations.commonProperties.properties = declarations.commonProperties.properties.concat(currentResult.commonProperties.properties);
179
+ }
180
+ return resolve(declarations);
181
+ });
182
+ });
183
+ }
184
+ }
@@ -0,0 +1,71 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import * as path from 'path';
4
+ import { Parser } from './parser';
5
+ import * as fileWriter from './file-writer';
6
+ import { resolveDeclarations, OutputtedDeclarations } from './declarations';
7
+ import { transformOutput } from './object-converter';
8
+ import { Events } from './events';
9
+ import { CommonProperties } from './common-properties';
10
+ import { TsParser } from './ts-parser';
11
+ import { patchDebugEvents } from './debug-patch';
12
+ import { ParserOptions, SourceSpec } from './source-spec';
13
+ import { logMessage } from './logger';
14
+
15
+ export function writeToFile(outputDir: string, contents: object, fileName: string, emitProgressMessage: boolean) {
16
+ const json = JSON.stringify(contents);
17
+ const outputFile = path.resolve(outputDir, `${fileName}.json`);
18
+ logMessage(`...writing ${outputFile}`, !emitProgressMessage);
19
+ return fileWriter.writeFile(outputFile, json);
20
+ }
21
+
22
+ export async function getResolvedDeclaration(sourceDirs: Array<string>, excludedDirs: Array<string>, options: ParserOptions) {
23
+ logMessage('...extracting', options.silenceOutput);
24
+ const parser = new Parser(sourceDirs, excludedDirs, options.applyEndpoints, options.lowerCaseEvents);
25
+ let declarations = await parser.extractDeclarations();
26
+ declarations = resolveDeclarations(declarations, options.verbose);
27
+ return declarations;
28
+ }
29
+
30
+ export async function extractAndResolveDeclarations(sourceSpecs: Array<SourceSpec>): Promise<{ events: any, commonProperties: any }> {
31
+ try {
32
+ const allDeclarations: OutputtedDeclarations = { events: new Events(), commonProperties: new CommonProperties() };
33
+ const allTypeScriptDeclarations = Object.create(null);
34
+ for (const spec of sourceSpecs) {
35
+ const declarations = await getResolvedDeclaration(spec.sourceDirs, spec.excludedDirs, spec.parserOptions);
36
+ let typescriptDeclarations = Object.create(null);
37
+ // 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
38
+ spec.sourceDirs.forEach((dir) => {
39
+ Object.assign(typescriptDeclarations, new TsParser(dir, spec.excludedDirs, spec.parserOptions.applyEndpoints, spec.parserOptions.lowerCaseEvents).parseFiles());
40
+ });
41
+ if (spec.parserOptions.eventPrefix !== '') {
42
+ declarations.events.dataPoints = declarations.events.dataPoints.map((event) => {
43
+ event.name = spec.parserOptions.eventPrefix + event.name;
44
+ return event;
45
+ });
46
+ const modifiedDeclartions = Object.create(null);
47
+ // Modify the object keys to be prefixed with the specified prefix
48
+ for (const key in typescriptDeclarations) {
49
+ modifiedDeclartions[spec.parserOptions.eventPrefix + key] = typescriptDeclarations[key];
50
+ }
51
+ typescriptDeclarations = modifiedDeclartions;
52
+ }
53
+ if (spec.parserOptions.patchDebugEvents) {
54
+ patchDebugEvents(declarations.events, spec.parserOptions.eventPrefix);
55
+ }
56
+ // We concatenate each extensions properties into a central one
57
+ // Throwing out fragments as they have already been used to resolve that extensions declarations
58
+ allDeclarations.commonProperties.properties = allDeclarations.commonProperties.properties.concat(declarations.commonProperties.properties);
59
+ allDeclarations.events.dataPoints = allDeclarations.events.dataPoints.concat(declarations.events.dataPoints);
60
+ Object.assign(allTypeScriptDeclarations, typescriptDeclarations);
61
+ }
62
+ const formattedDeclarations: any = await transformOutput(allDeclarations);
63
+ for (const dec in allTypeScriptDeclarations) {
64
+ formattedDeclarations.events[dec] = allTypeScriptDeclarations[dec];
65
+ }
66
+ return Promise.resolve(formattedDeclarations);
67
+ } catch (error) {
68
+ console.error(`Error: ${error}`);
69
+ return Promise.reject(error);
70
+ }
71
+ }
@@ -0,0 +1,67 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { PathLike, readFileSync } from 'fs';
4
+ import * as path from 'path';
5
+ import { cwd } from 'process';
6
+
7
+
8
+ export interface ParserOptions {
9
+ eventPrefix: string;
10
+ applyEndpoints: boolean;
11
+ patchDebugEvents: boolean;
12
+ lowerCaseEvents: boolean;
13
+ silenceOutput: boolean;
14
+ verbose: boolean;
15
+ }
16
+
17
+ export interface SourceSpec {
18
+ sourceDirs: string[],
19
+ excludedDirs: string[],
20
+ parserOptions: ParserOptions
21
+ };
22
+
23
+ export function convertConfigToSourceSpecs(file: PathLike): SourceSpec[] {
24
+ try {
25
+ const config = JSON.parse(readFileSync(file).toString());
26
+ const sourceSpecs: SourceSpec[] = [];
27
+ for (const key in config) {
28
+ const spec = config[key];
29
+ // Some defaults
30
+ spec.excludedDirs = spec.excludedDirs ? spec.excludedDirs : [];
31
+ spec.workingDir = spec.workingDir ? spec.workingDir : cwd();
32
+ spec.patchDebugEvents = spec.patchDebugEvents ? spec.patchDebugEvents : false;
33
+ spec.lowerCaseEvents = spec.lowerCaseEvents ? spec.lowerCaseEvents : false;
34
+ const parserOptions: ParserOptions = {
35
+ eventPrefix: spec.eventPrefix ? spec.eventPrefix : '',
36
+ applyEndpoints: spec.applyEndpoints,
37
+ patchDebugEvents: spec.patchDebugEvents,
38
+ lowerCaseEvents: spec.lowerCaseEvents,
39
+ silenceOutput: spec.silenceOuput,
40
+ verbose: spec.verbose
41
+ }
42
+ const sourceSpec: SourceSpec = {
43
+ sourceDirs: resolveDirectories(spec.sourceDirs, spec.workingDir),
44
+ excludedDirs: resolveDirectories(spec.excludedDirs, spec.workingDir),
45
+ parserOptions: parserOptions
46
+ }
47
+ sourceSpecs.push(sourceSpec);
48
+ }
49
+ return sourceSpecs;
50
+ } catch (err) {
51
+ console.error(err);
52
+ return [];
53
+ }
54
+ }
55
+
56
+ // Resolves an array of paths
57
+ function resolveDirectories(dirs: string[], workingDir?: string): string[] {
58
+ if (workingDir) {
59
+ if (path.isAbsolute(workingDir)) {
60
+ return dirs.map(s => path.resolve(workingDir, s));
61
+ } else {
62
+ return dirs.map(s => path.resolve(cwd(), workingDir, s));
63
+ }
64
+ } else {
65
+ return dirs.map(s => path.resolve(cwd(), s));
66
+ }
67
+ }
@@ -0,0 +1,40 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT license.
3
+ import { Wildcard } from "./events";
4
+
5
+ export interface ITelemetryData{
6
+ dataPoints: Array<ITelemetryDataPoint>
7
+ }
8
+
9
+ export interface ITelemetryDataPoint {
10
+ name: string;
11
+ properties: Array<IProperty | IInclude | IInline | Wildcard>;
12
+ }
13
+
14
+ export interface IProperty {
15
+ name: string;
16
+ purpose: string;
17
+ classification: string;
18
+ expiration?: string;
19
+ owner?: string;
20
+ comment?: string;
21
+ }
22
+
23
+ export interface IInclude {
24
+ includeNames: Array<string>;
25
+ }
26
+
27
+ export interface IInline {
28
+ inlineName: string;
29
+ inlines: Array<string>;
30
+ }
31
+
32
+ export interface IWildcard {
33
+ entries: Array<IWildcardEntry>;
34
+ }
35
+
36
+ export interface IWildcardEntry {
37
+ prefix: string;
38
+ classification: {classification: string, purpose: string};
39
+ endpoint: string | undefined;
40
+ }