@@ -7,34 +7,46 @@ import { fileURLToPath } from 'url';
7
7
const __filename = fileURLToPath ( import . meta. url ) ;
8
8
const __dirname = path . join ( path . dirname ( __filename ) , '..' ) ;
9
9
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
+ ] ;
11
14
12
- const program = ts . createProgram ( [ filePath ] , { } ) ;
13
- const sourceFile = program . getSourceFile ( filePath ) ;
15
+ const program = ts . createProgram ( filePaths , { } ) ;
14
16
const typeChecker = program . getTypeChecker ( ) ;
15
17
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 } */
21
23
let telemetryGlobalContext ;
22
24
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 } ` ) ;
34
29
}
35
- } ) ;
36
30
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 ) {
38
50
throw new Error ( 'Could not find the telemetry types' ) ;
39
51
}
40
52
@@ -45,13 +57,21 @@ markdown += '> This is a generated file. Do not edit.\n\n';
45
57
markdown += '## Global Attributes\n\n' ;
46
58
markdown += '> Global attributes are sent (if available) with every telemetry event\n\n' ;
47
59
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` ;
49
69
50
70
markdown += '## Events\n\n' ;
51
71
52
- const properties = typeChecker . getPropertiesOfType ( telemetryEventsType ) ;
72
+ const properties = typeChecker . getPropertiesOfType ( telemetryEvents . type ) ;
53
73
for ( const prop of properties ) {
54
- const propType = typeChecker . getTypeOfSymbolAtLocation ( prop , sourceFile ) ;
74
+ const propType = typeChecker . getTypeOfSymbolAtLocation ( prop , telemetryEvents . file ) ;
55
75
56
76
markdown += `### ${ prop . name } \n\n` ;
57
77
@@ -69,50 +89,108 @@ for (const prop of properties) {
69
89
} \n\n`;
70
90
}
71
91
72
- markdown += `${ expandType ( propType , '' ) } \n\n` ;
92
+ markdown += `${ expandType ( telemetryEvents . file , propType , '' ) } \n\n` ;
73
93
}
74
94
75
95
const outputPath = path . join ( __dirname , 'docs/telemetry-events.md' ) ;
76
96
fs . writeFileSync ( outputPath , markdown ) ;
77
97
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
+
79
110
let result = '' ;
80
111
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 ( ) ) {
82
139
if ( isRoot ) {
83
140
return type . types
84
- . map ( t => `\`\`\`typescript\n${ expandType ( t , '' , false , prefix ) } \n\`\`\`` )
141
+ . map ( t => `\`\`\`typescript\n${ expandType ( file , t , '' , false , prefix ) } \n\`\`\`` )
85
142
. join ( '\n\nor\n\n' ) ;
86
143
} 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 ( ' | ' ) ;
88
145
result = types . includes ( '\n' ) ? `(${ types } )` : types ;
89
146
}
90
147
} 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 ) {
93
151
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
+ }
97
164
}
98
- } ) ;
165
+ }
99
166
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 ) ;
103
170
const jsDocTags = getJSDocTags ( prop ) ;
104
171
let propString = '' ;
105
172
if ( jsDocTags . deprecated ) {
106
173
propString += `${ indent } // @deprecated: ${
107
174
jsDocTags . deprecated === true ? '' : jsDocTags . deprecated
108
175
} \n`;
109
176
}
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
+ ) } `;
111
184
return propString ;
112
185
} ) ;
186
+
187
+ if ( indexInfos . size ) {
188
+ expandedProps . push ( ...indexInfos . keys ( ) ) ;
189
+ }
190
+
113
191
result = `{\n${ expandedProps . join ( ',\n' ) } \n${ indent } }` ;
114
192
} 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 ( ' & ' ) ;
116
194
result = types . includes ( '\n' ) ? `(${ types } )` : types ;
117
195
}
118
196
} else if ( type . isStringLiteral ( ) ) {
@@ -127,40 +205,17 @@ function expandType(type, indent = '', isRoot = true, prefix = '') {
127
205
. map (
128
206
p =>
129
207
`'${ prefix } ${ p . name } ': ${ expandType (
130
- typeChecker . getTypeOfSymbolAtLocation ( p , sourceFile ) ,
208
+ file ,
209
+ typeChecker . getTypeOfSymbolAtLocation ( p , file ) ,
131
210
indent ,
132
211
false ,
133
212
prefix ,
134
213
) } `,
135
214
)
136
215
. join ( ', ' ) ;
137
- const returnType = expandType ( signatures [ 0 ] . getReturnType ( ) , indent , false , prefix ) ;
216
+ const returnType = expandType ( file , signatures [ 0 ] . getReturnType ( ) , indent , false , prefix ) ;
138
217
result = `(${ params } ) => ${ returnType } ` ;
139
218
}
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
- }
164
219
} else {
165
220
result = typeChecker . typeToString ( type ) ;
166
221
}
0 commit comments