17
17
* under the License.
18
18
*/
19
19
20
- const ts = require ( ' typescript' ) ;
21
- const fs = require ( 'fs' ) ;
20
+ const ts = require ( " typescript" ) ;
21
+ const fs = require ( "fs" ) ;
22
22
23
23
/* This library does three things to make openapi-typescript generation easier to use.
24
24
* 1. Creates capitalized exports for Paths and Operations
@@ -29,16 +29,16 @@ const fs = require('fs');
29
29
*/
30
30
31
31
/* Finds all words, capitalizes them, and removes all other characters. */
32
- const toPascalCase = ( str ) => (
32
+ const toPascalCase = ( str ) =>
33
33
( str . match ( / [ a - z A - Z 0 - 9 ] + / g) || [ ] )
34
34
. map ( ( w ) => `${ w . charAt ( 0 ) . toUpperCase ( ) } ${ w . slice ( 1 ) } ` )
35
- . join ( '' )
36
- ) ;
35
+ . join ( "" ) ;
37
36
38
37
/* Adds a prefix to a type prop as necessary.
39
38
* ('', 'components') => 'components'
40
39
*/
41
- const prefixPath = ( rootPrefix , prop ) => ( rootPrefix ? `${ rootPrefix } ['${ prop } ']` : prop ) ;
40
+ const prefixPath = ( rootPrefix , prop ) =>
41
+ rootPrefix ? `${ rootPrefix } ['${ prop } ']` : prop ;
42
42
43
43
// Recursively find child nodes by name.
44
44
const findNode = ( node , ...names ) => {
@@ -58,87 +58,114 @@ const findNode = (node, ...names) => {
58
58
// Generate Variable Type Aliases for a given path or operation
59
59
const generateVariableAliases = ( node , operationPath , operationName ) => {
60
60
const variableTypes = [ ] ;
61
- const hasPath = ! ! findNode ( node , 'parameters' , 'path' ) ;
62
- const hasQuery = ! ! findNode ( node , 'parameters' , 'query' ) ;
63
- const hasBody = ! ! findNode ( node , 'requestBody' , 'content' , 'application/json' ) ;
61
+ const hasPath = ! ! findNode ( node , "parameters" , "path" ) ;
62
+ const hasQuery = ! ! findNode ( node , "parameters" , "query" ) ;
63
+ const hasBody = ! ! findNode (
64
+ node ,
65
+ "requestBody" ,
66
+ "content" ,
67
+ "application/json"
68
+ ) ;
64
69
65
70
if ( hasPath ) variableTypes . push ( `${ operationPath } ['parameters']['path']` ) ;
66
71
if ( hasQuery ) variableTypes . push ( `${ operationPath } ['parameters']['query']` ) ;
67
- if ( hasBody ) variableTypes . push ( `${ operationPath } ['requestBody']['content']['application/json']` ) ;
72
+ if ( hasBody )
73
+ variableTypes . push (
74
+ `${ operationPath } ['requestBody']['content']['application/json']`
75
+ ) ;
68
76
69
- if ( variableTypes . length === 0 ) return '' ;
77
+ if ( variableTypes . length === 0 ) return "" ;
70
78
const typeName = `${ toPascalCase ( operationName ) } Variables` ;
71
- return [ typeName , `export type ${ typeName } = CamelCasedPropertiesDeep<${ variableTypes . join ( ' & ' ) } >;` ] ;
79
+ return [
80
+ typeName ,
81
+ `export type ${ typeName } = CamelCasedPropertiesDeep<${ variableTypes . join (
82
+ " & "
83
+ ) } >;`,
84
+ ] ;
72
85
} ;
73
86
74
87
// Generate Type Aliases
75
- const generateAliases = ( rootNode , writeText , prefix = '' ) => {
88
+ const generateAliases = ( rootNode , writeText , prefix = "" ) => {
76
89
// Loop through the root AST nodes of the file
77
90
ts . forEachChild ( rootNode , ( node ) => {
78
91
// Response Data Types
79
- if ( ts . isInterfaceDeclaration ( node ) && node . name ?. text === 'components' ) {
80
- const schemaMemberNames = findNode ( node , 'schemas' ) . type . members . map ( ( n ) => n . name ?. text ) ;
92
+ if ( ts . isInterfaceDeclaration ( node ) && node . name ?. text === "components" ) {
93
+ const schemaMemberNames = findNode ( node , "schemas" ) . type . members . map (
94
+ ( n ) => n . name ?. text
95
+ ) ;
81
96
82
97
const types = schemaMemberNames . map ( ( n ) => [
83
98
`${ n } ` ,
84
- `export type ${ n } = CamelCasedPropertiesDeep<${ prefixPath ( prefix , 'components' ) } ['schemas']['${ n } ']>;` ,
99
+ `export type ${ n } = CamelCasedPropertiesDeep<${ prefixPath (
100
+ prefix ,
101
+ "components"
102
+ ) } ['schemas']['${ n } ']>;`,
85
103
] ) ;
86
104
if ( types . length ) {
87
- writeText . push ( [ ' comment' , `Types for returned data ${ prefix } ` ] ) ;
105
+ writeText . push ( [ " comment" , `Types for returned data ${ prefix } ` ] ) ;
88
106
writeText . push ( ...types ) ;
89
107
}
90
108
}
91
109
92
110
// Paths referencing an operation are skipped
93
- if ( node . name ?. text === ' paths' ) {
111
+ if ( node . name ?. text === " paths" ) {
94
112
if ( ! prefix ) {
95
- writeText . push ( [ ' comment' , ' Alias paths to PascalCase.' ] ) ;
96
- writeText . push ( [ ' Paths' , ' export type Paths = paths;' ] ) ;
113
+ writeText . push ( [ " comment" , " Alias paths to PascalCase." ] ) ;
114
+ writeText . push ( [ " Paths" , " export type Paths = paths;" ] ) ;
97
115
}
98
116
99
117
const types = [ ] ;
100
118
101
119
( node . members || node . type . members ) . forEach ( ( path ) => {
102
120
const methodNames = path . type . members . map ( ( m ) => m . name . text ) ;
103
- const methodTypes = methodNames . map ( ( m ) => (
121
+ const methodTypes = methodNames . map ( ( m ) =>
104
122
generateVariableAliases (
105
123
findNode ( path , m ) ,
106
- `${ prefixPath ( prefix , 'paths' ) } ['${ path . name ?. text } ']['${ m } ']` ,
107
- `${ path . name . text } ${ toPascalCase ( m ) } ` ,
108
- ) ) ) ;
124
+ `${ prefixPath ( prefix , "paths" ) } ['${ path . name ?. text } ']['${ m } ']` ,
125
+ `${ path . name . text } ${ toPascalCase ( m ) } `
126
+ )
127
+ ) ;
109
128
types . push ( ...methodTypes . filter ( ( m ) => ! ! m ) ) ;
110
129
} ) ;
111
130
112
131
if ( types . length ) {
113
- writeText . push ( [ 'comment' , `Types for path operation variables ${ prefix } ` ] ) ;
132
+ writeText . push ( [
133
+ "comment" ,
134
+ `Types for path operation variables ${ prefix } ` ,
135
+ ] ) ;
114
136
writeText . push ( ...types ) ;
115
137
}
116
138
}
117
139
118
140
// operationIds are defined
119
- if ( node . name ?. text === ' operations' ) {
141
+ if ( node . name ?. text === " operations" ) {
120
142
if ( ! prefix ) {
121
- writeText . push ( [ ' comment' , ' Alias operations to PascalCase.' ] ) ;
122
- writeText . push ( [ ' Operations' , ' export type Operations = operations;' ] ) ;
143
+ writeText . push ( [ " comment" , " Alias operations to PascalCase." ] ) ;
144
+ writeText . push ( [ " Operations" , " export type Operations = operations;" ] ) ;
123
145
}
124
146
125
- const types = ( node . members || node . type . members ) . map ( ( operation ) => (
147
+ const types = ( node . members || node . type . members ) . map ( ( operation ) =>
126
148
generateVariableAliases (
127
149
operation ,
128
- `${ prefixPath ( prefix , 'operations' ) } ['${ operation . name . text } ']` ,
129
- operation . name . text ,
130
- ) ) ) ;
150
+ `${ prefixPath ( prefix , "operations" ) } ['${ operation . name . text } ']` ,
151
+ operation . name . text
152
+ )
153
+ ) ;
131
154
if ( types . length ) {
132
- writeText . push ( [ ' comment' , `Types for operation variables ${ prefix } ` ] ) ;
155
+ writeText . push ( [ " comment" , `Types for operation variables ${ prefix } ` ] ) ;
133
156
writeText . push ( ...types ) ;
134
- writeText . push ( '\n' ) ;
157
+ writeText . push ( "\n" ) ;
135
158
}
136
159
}
137
160
138
161
// recursively call this for any externals
139
- if ( ts . isInterfaceDeclaration ( node ) && node . name ?. text === ' external' ) {
162
+ if ( ts . isInterfaceDeclaration ( node ) && node . name ?. text === " external" ) {
140
163
node . members . forEach ( ( external ) => {
141
- generateAliases ( external . type , writeText , `external['${ external . name . text } ']` ) ;
164
+ generateAliases (
165
+ external . type ,
166
+ writeText ,
167
+ `external['${ external . name . text } ']`
168
+ ) ;
142
169
} ) ;
143
170
}
144
171
} ) ;
@@ -169,27 +196,32 @@ function generate(file) {
169
196
const program = ts . createProgram ( [ file ] , { allowJs : true } ) ;
170
197
const sourceFile = program . getSourceFile ( file ) ;
171
198
const writeText = [ ] ;
172
- writeText . push ( [ ' block' , license ] ) ;
173
- writeText . push ( [ ' comment' , ' eslint-disable' ] ) ;
199
+ writeText . push ( [ " block" , license ] ) ;
200
+ writeText . push ( [ " comment" , " eslint-disable" ] ) ;
174
201
// eslint-disable-next-line quotes
175
- writeText . push ( [ 'block' , `import type { CamelCasedPropertiesDeep } from 'type-fest';` ] ) ;
176
- writeText . push ( [ 'block' , sourceFile . text ] ) ;
202
+ writeText . push ( [
203
+ "block" ,
204
+ `import type { CamelCasedPropertiesDeep } from 'type-fest';` ,
205
+ ] ) ;
206
+ writeText . push ( [ "block" , sourceFile . text ] ) ;
177
207
generateAliases ( sourceFile , writeText ) ;
178
208
179
209
const finalText = writeText
180
210
// Deduplicate types
181
211
. map ( ( pair ) => {
182
212
// keep all comments and code blocks
183
- if ( pair [ 0 ] === ' comment' || pair [ 0 ] === ' block' ) return pair ;
213
+ if ( pair [ 0 ] === " comment" || pair [ 0 ] === " block" ) return pair ;
184
214
// return the first instance of this key only
185
215
const firstInstance = writeText . find ( ( p ) => p [ 0 ] === pair [ 0 ] ) ;
186
- return firstInstance === pair ? pair : [ 'comment' , `Duplicate removed: ${ pair [ 1 ] } ` ] ;
216
+ return firstInstance === pair
217
+ ? pair
218
+ : [ "comment" , `Duplicate removed: ${ pair [ 1 ] } ` ] ;
187
219
} )
188
220
// Remove undefined created above
189
221
. filter ( ( p ) => ! ! p )
190
222
// Escape comments and flatten.
191
- . map ( ( pair ) => ( pair [ 0 ] === ' comment' ? `\n/* ${ pair [ 1 ] } */` : pair [ 1 ] ) )
192
- . join ( '\n' ) ;
223
+ . map ( ( pair ) => ( pair [ 0 ] === " comment" ? `\n/* ${ pair [ 1 ] } */` : pair [ 1 ] ) )
224
+ . join ( "\n" ) ;
193
225
194
226
fs . writeFileSync ( file , finalText , ( err ) => {
195
227
if ( err ) {
0 commit comments