1
1
import * as fs from 'fs' ;
2
2
import * as babylon from 'babylon' ;
3
3
4
+ type InstanceOfResolver = ( name : string ) => string ;
5
+
6
+ export interface IOptions {
7
+ /**
8
+ * Resolves type names to import paths.
9
+ *
10
+ * @return Path to given name if resolveable, undefined otherwise
11
+ */
12
+ instanceOfResolver ?: InstanceOfResolver ;
13
+ /**
14
+ * The writer generating .d.ts code with.
15
+ */
16
+ writer ?: Writer ;
17
+ }
18
+
4
19
interface IASTNode {
5
20
type : string ;
6
21
loc : Object ;
7
22
[ name : string ] : any ;
8
23
}
9
24
10
- interface IProp {
25
+ export interface IProp {
11
26
type : string ;
12
27
optional : boolean ;
28
+ importType ?: string ;
29
+ importPath ?: string ;
13
30
}
14
31
15
- interface IPropTypes {
32
+ export interface IPropTypes {
16
33
[ name : string ] : IProp ;
17
34
}
18
35
@@ -32,15 +49,15 @@ export function cli(options: any): void {
32
49
console . error ( 'Failed to specify --name parameter' ) ;
33
50
process . exit ( 1 ) ;
34
51
}
35
- process . stdout . write ( generate ( options . name , stdinCode . join ( '' ) ) ) ;
52
+ process . stdout . write ( generateFromSource ( options . name , stdinCode . join ( '' ) ) ) ;
36
53
} ) ;
37
54
}
38
55
39
- export function generateFromFile ( name : string , path : string ) : string {
40
- return generate ( name , fs . readFileSync ( path ) . toString ( ) ) ;
56
+ export function generateFromFile ( name : string , path : string , options ?: IOptions ) : string {
57
+ return generateFromSource ( name , fs . readFileSync ( path ) . toString ( ) , options ) ;
41
58
}
42
59
43
- export function generate ( name : string , code : string ) : string {
60
+ export function generateFromSource ( name : string , code : string , options : IOptions = { } ) : string {
44
61
const ast : any = babylon . parse ( code , {
45
62
sourceType : 'module' ,
46
63
plugins : [
@@ -59,42 +76,69 @@ export function generate(name: string, code: string): string {
59
76
'functionSent'
60
77
]
61
78
} ) ;
62
- const writer : Writer = new Writer ( ) ;
79
+ return generateFromAst ( name , ast , options ) ;
80
+ }
81
+
82
+ const defaultInstanceOfResolver : InstanceOfResolver = ( name : string ) : string => undefined ;
83
+
84
+ export function generateFromAst ( name : string , ast : any , options : IOptions = { } ) : string {
85
+ const { classname, propTypes} : IParsingResult = parseAst ( ast , options ) ;
86
+ const writer : Writer = options . writer || new Writer ( ) ;
63
87
writer . declareModule ( name , ( ) => {
64
88
writer . import ( '* as React' , 'react' ) ;
89
+ if ( propTypes ) {
90
+ Object . keys ( propTypes ) . forEach ( ( propName : string ) => {
91
+ const prop : IProp = propTypes [ propName ] ;
92
+ if ( prop . importPath ) {
93
+ writer . import ( prop . importType , prop . importPath ) ;
94
+ }
95
+ } ) ;
96
+ }
65
97
writer . nl ( ) ;
66
- walk ( ast . program , {
67
- 'ExportDefaultDeclaration' : ( node : any ) => {
68
- let classname : string ;
69
- let propTypes : IPropTypes = undefined ;
70
- walk ( node , {
71
- 'ClassDeclaration' : ( node : any ) => {
72
- classname = node . id . name ;
73
- walk ( node . body , {
74
- 'ClassProperty' : ( node : any ) => {
75
- if ( node . key . name == 'propTypes' ) {
76
- propTypes = { } ;
77
- walk ( node . value , {
78
- 'ObjectProperty' : ( node : any ) => {
79
- propTypes [ node . key . name ] = getTypeFromPropType ( node . value ) ;
80
- }
81
- } ) ;
82
- }
83
- }
84
- } ) ;
85
- writer . props ( classname , propTypes ) ;
86
- writer . nl ( ) ;
87
- }
88
- } ) ;
89
- writer . exportDefault ( ( ) => {
90
- writer . class ( classname , ! ! propTypes ) ;
91
- } ) ;
92
- }
98
+ writer . props ( classname , propTypes ) ;
99
+ writer . nl ( ) ;
100
+ writer . exportDefault ( ( ) => {
101
+ writer . class ( classname , ! ! propTypes ) ;
93
102
} ) ;
94
103
} ) ;
95
104
return writer . toString ( ) ;
96
105
}
97
106
107
+ interface IParsingResult {
108
+ classname : string ;
109
+ propTypes : IPropTypes ;
110
+ }
111
+
112
+ function parseAst ( ast : any , options : IOptions ) : IParsingResult {
113
+ let classname : string ;
114
+ let propTypes : IPropTypes = undefined ;
115
+ walk ( ast . program , {
116
+ 'ExportDefaultDeclaration' : ( node : IASTNode ) => {
117
+ walk ( node , {
118
+ 'ClassDeclaration' : ( node : any ) => {
119
+ classname = node . id . name ;
120
+ walk ( node . body , {
121
+ 'ClassProperty' : ( node : any ) => {
122
+ if ( node . key . name == 'propTypes' ) {
123
+ propTypes = { } ;
124
+ walk ( node . value , {
125
+ 'ObjectProperty' : ( node : any ) => {
126
+ propTypes [ node . key . name ] = getTypeFromPropType ( node . value , options . instanceOfResolver ) ;
127
+ }
128
+ } ) ;
129
+ }
130
+ }
131
+ } ) ;
132
+ }
133
+ } ) ;
134
+ }
135
+ } ) ;
136
+ return {
137
+ classname,
138
+ propTypes
139
+ } ;
140
+ }
141
+
98
142
function walk ( node : IASTNode , handlers : any ) : void {
99
143
if ( isNode ( node ) ) {
100
144
if ( typeof handlers [ node . type ] == 'function' ) {
@@ -117,45 +161,51 @@ function isNode(obj: IASTNode): boolean {
117
161
return obj && typeof obj . type != 'undefined' && typeof obj . loc != 'undefined' ;
118
162
}
119
163
120
- function getReactPropTypeFromExpression ( node : any ) : any {
164
+ function getReactPropTypeFromExpression ( node : any , instanceOfResolver : InstanceOfResolver ) : any {
121
165
if ( node . type == 'MemberExpression' && node . object . type == 'MemberExpression'
122
166
&& node . object . object . name == 'React' && node . object . property . name == 'PropTypes' ) {
123
167
return node . property ;
124
168
} else if ( node . type == 'CallExpression' ) {
125
- const callType : any = getReactPropTypeFromExpression ( node . callee ) ;
169
+ const callType : any = getReactPropTypeFromExpression ( node . callee , instanceOfResolver ) ;
126
170
switch ( callType . name ) {
171
+ case 'instanceOf' :
172
+ return {
173
+ name : 'instanceOf' ,
174
+ type : node . arguments [ 0 ] . name ,
175
+ importPath : instanceOfResolver ( node . arguments [ 0 ] . name )
176
+ } ;
127
177
case 'arrayOf' :
128
178
return {
129
179
name : 'array' ,
130
- arrayType : getTypeFromPropType ( node . arguments [ 0 ] ) . type
180
+ arrayType : getTypeFromPropType ( node . arguments [ 0 ] , instanceOfResolver ) . type
131
181
} ;
132
182
case 'oneOfType' :
133
183
return {
134
184
name : 'union' ,
135
185
types : node . arguments [ 0 ] . elements . map ( ( element : IASTNode ) => {
136
- return getTypeFromPropType ( element ) . type ;
186
+ return getTypeFromPropType ( element , instanceOfResolver ) . type ;
137
187
} )
138
188
} ;
139
189
}
140
190
}
141
191
return 'undefined' ;
142
192
}
143
193
144
- function isRequiredPropType ( node : any ) : any {
194
+ function isRequiredPropType ( node : any , instanceOfResolver : InstanceOfResolver ) : any {
145
195
const isRequired : boolean = node . type == 'MemberExpression' && node . property . name == 'isRequired' ;
146
196
return {
147
197
isRequired,
148
- type : getReactPropTypeFromExpression ( isRequired ? node . object : node )
198
+ type : getReactPropTypeFromExpression ( isRequired ? node . object : node , instanceOfResolver )
149
199
} ;
150
200
}
151
201
152
- export function getTypeFromPropType ( node : IASTNode ) : IProp {
202
+ export function getTypeFromPropType ( node : IASTNode , instanceOfResolver : InstanceOfResolver = defaultInstanceOfResolver ) : IProp {
153
203
const result : any = {
154
204
type : 'any' ,
155
205
optional : true
156
206
} ;
157
207
if ( isNode ( node ) ) {
158
- const { isRequired, type} : any = isRequiredPropType ( node ) ;
208
+ const { isRequired, type} : any = isRequiredPropType ( node , instanceOfResolver ) ;
159
209
result . optional = ! isRequired ;
160
210
switch ( type . name ) {
161
211
case 'any' :
@@ -188,6 +238,15 @@ export function getTypeFromPropType(node: IASTNode): IProp {
188
238
case 'union' :
189
239
result . type = type . types . map ( ( unionType : string ) => unionType ) . join ( '|' ) ;
190
240
break ;
241
+ case 'instanceOf' :
242
+ if ( type . importPath ) {
243
+ result . type = 'typeof ' + type . type ;
244
+ result . importType = type . type ;
245
+ result . importPath = type . importPath ;
246
+ } else {
247
+ result . type = 'any' ;
248
+ }
249
+ break ;
191
250
}
192
251
}
193
252
return result ;
0 commit comments