@@ -7,34 +7,46 @@ import { fileURLToPath } from 'url';
77const __filename = fileURLToPath ( import . meta. url ) ;
88const __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 , { } ) ;
1416const 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 } */
2123let 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';
4557markdown += '## Global Attributes\n\n' ;
4658markdown += '> 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
5070markdown += '## Events\n\n' ;
5171
52- const properties = typeChecker . getPropertiesOfType ( telemetryEventsType ) ;
72+ const properties = typeChecker . getPropertiesOfType ( telemetryEvents . type ) ;
5373for ( 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
7595const outputPath = path . join ( __dirname , 'docs/telemetry-events.md' ) ;
7696fs . 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 }
0 commit comments