Skip to content

Commit dc4c15d

Browse files
committed
Updates feature preview tracking
1 parent 2a9e0b8 commit dc4c15d

File tree

9 files changed

+419
-171
lines changed

9 files changed

+419
-171
lines changed

docs/telemetry-events.md

Lines changed: 151 additions & 18 deletions
Large diffs are not rendered by default.

scripts/generate-telemetry-docs.mjs

Lines changed: 121 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,46 @@ import { fileURLToPath } from 'url';
77
const __filename = fileURLToPath(import.meta.url);
88
const __dirname = path.join(path.dirname(__filename), '..');
99

10-
const filePath = path.join(__dirname, 'src/constants.telemetry.ts');
10+
const filePaths = [
11+
path.join(__dirname, 'src/telemetry/telemetry.ts'),
12+
path.join(__dirname, 'src/constants.telemetry.ts'),
13+
];
1114

12-
const program = ts.createProgram([filePath], {});
13-
const sourceFile = program.getSourceFile(filePath);
15+
const program = ts.createProgram(filePaths, {});
1416
const typeChecker = program.getTypeChecker();
1517

16-
if (!sourceFile) {
17-
throw new Error(`Could not find source file: ${filePath}`);
18-
}
19-
20-
let telemetryEventsType;
18+
/** @type {{ file: ts.SourceFile, type: ts.Type } | undefined} */
19+
let telemetryContext;
20+
/** @type {{ file: ts.SourceFile, type: ts.Type } | undefined} */
21+
let telemetryEvents;
22+
/** @type {{ file: ts.SourceFile, type: ts.Type } | undefined} */
2123
let telemetryGlobalContext;
2224

23-
// Find the types
24-
ts.forEachChild(sourceFile, node => {
25-
if (ts.isTypeAliasDeclaration(node)) {
26-
switch (node.name.text) {
27-
case 'TelemetryEvents':
28-
telemetryEventsType = typeChecker.getTypeAtLocation(node);
29-
break;
30-
case 'TelemetryGlobalContext':
31-
telemetryGlobalContext = typeChecker.getTypeAtLocation(node);
32-
break;
33-
}
25+
for (const filePath of filePaths) {
26+
const sourceFile = program.getSourceFile(filePath);
27+
if (!sourceFile) {
28+
throw new Error(`Could not find source file: ${filePath}`);
3429
}
35-
});
3630

37-
if (!telemetryEventsType || !telemetryGlobalContext) {
31+
// Find the types
32+
ts.forEachChild(sourceFile, node => {
33+
if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) {
34+
switch (node.name.text) {
35+
case 'TelemetryContext':
36+
telemetryContext = { file: sourceFile, type: typeChecker.getTypeAtLocation(node) };
37+
break;
38+
case 'TelemetryEvents':
39+
telemetryEvents = { file: sourceFile, type: typeChecker.getTypeAtLocation(node) };
40+
break;
41+
case 'TelemetryGlobalContext':
42+
telemetryGlobalContext = { file: sourceFile, type: typeChecker.getTypeAtLocation(node) };
43+
break;
44+
}
45+
}
46+
});
47+
}
48+
49+
if (!telemetryContext || !telemetryEvents || !telemetryGlobalContext) {
3850
throw new Error('Could not find the telemetry types');
3951
}
4052

@@ -45,13 +57,21 @@ markdown += '> This is a generated file. Do not edit.\n\n';
4557
markdown += '## Global Attributes\n\n';
4658
markdown += '> Global attributes are sent (if available) with every telemetry event\n\n';
4759

48-
markdown += `${expandType(telemetryGlobalContext, '', true, 'global.')}\n\n`;
60+
markdown += '```typescript\n';
61+
62+
let result = expandType(telemetryContext.file, telemetryContext.type, '', false);
63+
result = result.substring(0, result.lastIndexOf('}')); // Strip trailing `}`
64+
markdown += `${result}`;
65+
66+
result = expandType(telemetryGlobalContext.file, telemetryGlobalContext.type, '', false, 'global.');
67+
result = result.substring(1); // Strip leading `{`
68+
markdown += `${result}\n\`\`\`\n\n`;
4969

5070
markdown += '## Events\n\n';
5171

52-
const properties = typeChecker.getPropertiesOfType(telemetryEventsType);
72+
const properties = typeChecker.getPropertiesOfType(telemetryEvents.type);
5373
for (const prop of properties) {
54-
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, sourceFile);
74+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, telemetryEvents.file);
5575

5676
markdown += `### ${prop.name}\n\n`;
5777

@@ -69,50 +89,108 @@ for (const prop of properties) {
6989
}\n\n`;
7090
}
7191

72-
markdown += `${expandType(propType, '')}\n\n`;
92+
markdown += `${expandType(telemetryEvents.file, propType, '')}\n\n`;
7393
}
7494

7595
const outputPath = path.join(__dirname, 'docs/telemetry-events.md');
7696
fs.writeFileSync(outputPath, markdown);
7797

78-
function expandType(type, indent = '', isRoot = true, prefix = '') {
98+
/**
99+
* @param {ts.SourceFile} file
100+
* @param {ts.Type} type
101+
* @param {string} indent
102+
* @param {boolean} isRoot
103+
* @param {string} prefix
104+
*/
105+
function expandType(file, type, indent = '', isRoot = true, prefix = '') {
106+
if (type.flags & ts.TypeFlags.Boolean) {
107+
return 'boolean';
108+
}
109+
79110
let result = '';
80111

81-
if (type.isUnion()) {
112+
if (type.isClassOrInterface() || (type.symbol && type.symbol.flags & ts.SymbolFlags.TypeLiteral)) {
113+
const properties = typeChecker.getPropertiesOfType(type);
114+
if (!properties?.length) {
115+
result = '{}';
116+
} else {
117+
let expandedProps = properties.map(prop => {
118+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, file);
119+
const jsDocTags = getJSDocTags(prop);
120+
let propString = '';
121+
if (jsDocTags.deprecated) {
122+
propString += `${indent} // @deprecated: ${
123+
jsDocTags.deprecated === true ? '' : jsDocTags.deprecated
124+
}\n`;
125+
}
126+
propString += `${indent} '${prefix}${prop.name}': ${expandType(
127+
file,
128+
propType,
129+
indent + ' ',
130+
false,
131+
prefix,
132+
)}`;
133+
return propString;
134+
});
135+
136+
result = `{\n${expandedProps.join(',\n')}\n${indent}}`;
137+
}
138+
} else if (type.isUnion()) {
82139
if (isRoot) {
83140
return type.types
84-
.map(t => `\`\`\`typescript\n${expandType(t, '', false, prefix)}\n\`\`\``)
141+
.map(t => `\`\`\`typescript\n${expandType(file, t, '', false, prefix)}\n\`\`\``)
85142
.join('\n\nor\n\n');
86143
} else {
87-
const types = type.types.map(t => expandType(t, indent, false, prefix)).join(' | ');
144+
const types = type.types.map(t => expandType(file, t, indent, false, prefix)).join(' | ');
88145
result = types.includes('\n') ? `(${types})` : types;
89146
}
90147
} else if (type.isIntersection()) {
91-
const combinedProperties = new Map();
92-
type.types.forEach(t => {
148+
const mergedProperties = new Map();
149+
const indexInfos = new Set();
150+
for (const t of type.types) {
93151
if (t.symbol && t.symbol.flags & ts.SymbolFlags.TypeLiteral) {
94-
typeChecker.getPropertiesOfType(t).forEach(prop => {
95-
combinedProperties.set(prop.name, prop);
96-
});
152+
for (const prop of typeChecker.getPropertiesOfType(t)) {
153+
mergedProperties.set(prop.name, prop);
154+
}
155+
156+
for (const indexInfo of typeChecker.getIndexInfosOfType(t)) {
157+
let keyType = typeChecker.typeToString(indexInfo.keyType);
158+
if (prefix) {
159+
keyType = `\`${prefix}${keyType.substring(1)}`;
160+
}
161+
const valueType = expandType(file, indexInfo.type, indent + ' ', false, prefix);
162+
indexInfos.add(`${indent} [${keyType}]: ${valueType}`);
163+
}
97164
}
98-
});
165+
}
99166

100-
if (combinedProperties.size > 0) {
101-
const expandedProps = Array.from(combinedProperties).map(([name, prop]) => {
102-
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, sourceFile);
167+
if (mergedProperties.size) {
168+
const expandedProps = [...mergedProperties].map(([name, prop]) => {
169+
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, file);
103170
const jsDocTags = getJSDocTags(prop);
104171
let propString = '';
105172
if (jsDocTags.deprecated) {
106173
propString += `${indent} // @deprecated: ${
107174
jsDocTags.deprecated === true ? '' : jsDocTags.deprecated
108175
}\n`;
109176
}
110-
propString += `${indent} '${prefix}${name}': ${expandType(propType, indent + ' ', false, prefix)}`;
177+
propString += `${indent} '${prefix}${name}': ${expandType(
178+
file,
179+
propType,
180+
indent + ' ',
181+
false,
182+
prefix,
183+
)}`;
111184
return propString;
112185
});
186+
187+
if (indexInfos.size) {
188+
expandedProps.push(...indexInfos.keys());
189+
}
190+
113191
result = `{\n${expandedProps.join(',\n')}\n${indent}}`;
114192
} else {
115-
const types = type.types.map(t => expandType(t, indent, false, prefix)).join(' & ');
193+
const types = type.types.map(t => expandType(file, t, indent, false, prefix)).join(' & ');
116194
result = types.includes('\n') ? `(${types})` : types;
117195
}
118196
} else if (type.isStringLiteral()) {
@@ -127,40 +205,17 @@ function expandType(type, indent = '', isRoot = true, prefix = '') {
127205
.map(
128206
p =>
129207
`'${prefix}${p.name}': ${expandType(
130-
typeChecker.getTypeOfSymbolAtLocation(p, sourceFile),
208+
file,
209+
typeChecker.getTypeOfSymbolAtLocation(p, file),
131210
indent,
132211
false,
133212
prefix,
134213
)}`,
135214
)
136215
.join(', ');
137-
const returnType = expandType(signatures[0].getReturnType(), indent, false, prefix);
216+
const returnType = expandType(file, signatures[0].getReturnType(), indent, false, prefix);
138217
result = `(${params}) => ${returnType}`;
139218
}
140-
} else if (type.symbol && type.symbol.flags & ts.SymbolFlags.TypeLiteral) {
141-
const properties = typeChecker.getPropertiesOfType(type);
142-
if (properties.length === 0) {
143-
result = '{}';
144-
} else {
145-
const expandedProps = properties.map(prop => {
146-
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, sourceFile);
147-
const jsDocTags = getJSDocTags(prop);
148-
let propString = '';
149-
if (jsDocTags.deprecated) {
150-
propString += `${indent} // @deprecated: ${
151-
jsDocTags.deprecated === true ? '' : jsDocTags.deprecated
152-
}\n`;
153-
}
154-
propString += `${indent} '${prefix}${prop.name}': ${expandType(
155-
propType,
156-
indent + ' ',
157-
false,
158-
prefix,
159-
)}`;
160-
return propString;
161-
});
162-
result = `{\n${expandedProps.join(',\n')}\n${indent}}`;
163-
}
164219
} else {
165220
result = typeChecker.typeToString(type);
166221
}

src/constants.subscription.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,14 @@ export const enum SubscriptionState {
3333
/** Indicates a Pro/Teams/Enterprise paid user */
3434
Paid = 6,
3535
}
36+
37+
export type SubscriptionStateString =
38+
| 'verification'
39+
| 'free'
40+
| 'preview'
41+
| 'preview-expired'
42+
| 'trial'
43+
| 'trial-expired'
44+
| 'trial-reactivation-eligible'
45+
| 'paid'
46+
| 'unknown';

0 commit comments

Comments
 (0)