@@ -73,10 +73,12 @@ export function createTypings(
73
73
}
74
74
const alreadyDefined : string [ ] = [ ] ;
75
75
76
+ const componentDots = getComponentDotProperties ( ast , componentNames ) ;
76
77
componentNames . forEach ( ( componentName ) => {
77
78
const exportType = getComponentExportType ( ast , componentName ) ;
78
79
const propTypes = getPropTypes ( ast , componentName ) ;
79
- if ( exportType ) {
80
+ const intersection = getIntersection ( componentDots , componentName ) ;
81
+ if ( exportType || componentDots . length ) {
80
82
alreadyDefined . push ( componentName ) ;
81
83
createExportedTypes (
82
84
m ,
@@ -86,6 +88,7 @@ export function createTypings(
86
88
propTypes ,
87
89
importedPropTypes ,
88
90
exportType ,
91
+ intersection ,
89
92
options
90
93
) ;
91
94
}
@@ -94,49 +97,56 @@ export function createTypings(
94
97
// top level object variables
95
98
const componentObject = getComponentNamesByObject ( ast , componentNames ) ;
96
99
97
- componentObject . forEach ( ( { name, properties = { } } ) => {
100
+ componentObject . forEach ( ( { name, properties } ) => {
98
101
const obj = dom . create . objectType ( [ ] ) ;
99
102
let hasType ;
100
103
101
104
Object . keys ( properties ) . forEach ( ( k ) => {
102
105
const { key, value } = properties [ k ] ;
103
- componentNames . forEach ( ( componentName ) => {
104
- // if a property matches an existing component
105
- // add it to the object definition
106
- if ( value . type === 'Identifier' && value . name === componentName ) {
107
- const exportType = getComponentExportType ( ast , componentName ) ;
108
- const propTypes = getPropTypes ( ast , value . name ) ;
109
- // if it was exported individually, it will already have been typed earlier
110
- if ( ! alreadyDefined . includes ( componentName ) ) {
111
- createExportedTypes (
112
- m ,
113
- ast ,
114
- value . name ,
115
- reactComponentName ,
116
- propTypes ,
117
- importedPropTypes ,
118
- exportType ,
119
- options
120
- ) ;
121
- }
122
-
123
- if ( propTypes ) {
124
- hasType = true ;
125
- const type1 = dom . create . namedTypeReference ( value . name ) ;
126
- const typeBase = dom . create . typeof ( type1 ) ;
127
- const b = dom . create . property ( key . name , typeBase ) ;
128
- obj . members . push ( b ) ;
129
- }
106
+ // if a property matches an existing component
107
+ // add it to the object definition
108
+ if ( value . type === 'Identifier' && componentNames . includes ( value . name ) ) {
109
+ const exportType =
110
+ name === '_default'
111
+ ? undefined
112
+ : getComponentExportType ( ast , value . name ) ;
113
+ const propTypes = getPropTypes ( ast , value . name ) ;
114
+ const intersection = getIntersection ( componentDots , name ) ;
115
+
116
+ // if it was exported individually, it will already have been typed earlier
117
+ if ( ! alreadyDefined . includes ( value . name ) ) {
118
+ createExportedTypes (
119
+ m ,
120
+ ast ,
121
+ value . name ,
122
+ reactComponentName ,
123
+ propTypes ,
124
+ importedPropTypes ,
125
+ exportType ,
126
+ intersection ,
127
+ options
128
+ ) ;
130
129
}
131
- } ) ;
130
+
131
+ if ( propTypes ) {
132
+ hasType = true ;
133
+ const type1 = dom . create . namedTypeReference ( value . name ) ;
134
+ const typeBase = dom . create . typeof ( type1 ) ;
135
+ const b = dom . create . property ( key . name , typeBase ) ;
136
+ obj . members . push ( b ) ;
137
+ }
138
+ }
132
139
} ) ;
133
140
if ( hasType ) {
134
141
const exportType = getComponentExportType ( ast , name ) ;
135
142
136
143
const objConst = dom . create . const ( name , obj ) ;
137
144
m . members . push ( objConst ) ;
138
145
139
- if ( exportType === dom . DeclarationFlags . ExportDefault ) {
146
+ if (
147
+ exportType === dom . DeclarationFlags . ExportDefault ||
148
+ name === '_default'
149
+ ) {
140
150
m . members . push ( dom . create . exportDefault ( name ) ) ;
141
151
} else {
142
152
objConst . flags = exportType ;
@@ -150,6 +160,24 @@ export function createTypings(
150
160
return dom . emit ( m , { tripleSlashDirectives } ) ;
151
161
}
152
162
}
163
+ function getIntersection (
164
+ componentDots : ComponentProperties [ ] ,
165
+ componentName : string
166
+ ) : string | null {
167
+ const intersection = componentDots . find ( ( v ) => v . name === componentName ) ;
168
+ if ( intersection ) {
169
+ const types = intersection . properties . map (
170
+ ( prop : ComponentProperties [ 'properties' ] [ 0 ] ) => {
171
+ return `\t\t${ prop . key } : typeof ${ prop . value } ;` ;
172
+ }
173
+ ) ;
174
+
175
+ return ` & {
176
+ ${ types . join ( '\n' ) }
177
+ }` ;
178
+ }
179
+ return null ;
180
+ }
153
181
154
182
function createExportedTypes (
155
183
m : dom . ModuleDeclaration ,
@@ -159,6 +187,7 @@ function createExportedTypes(
159
187
propTypes : any ,
160
188
importedPropTypes : ImportedPropTypes ,
161
189
exportType : dom . DeclarationFlags | undefined ,
190
+ intersection : any ,
162
191
options : IOptions
163
192
) : void {
164
193
const classComponent = isClassComponent (
@@ -177,32 +206,71 @@ function createExportedTypes(
177
206
if ( propTypes || classComponent ) {
178
207
m . members . push ( interf ) ;
179
208
}
180
-
181
209
if ( classComponent ) {
182
- if ( ! exportType ) {
183
- createClassComponent ( m , componentName , reactComponentName , interf ) ;
184
- } else {
185
- createExportedClassComponent (
186
- m ,
187
- componentName ,
188
- reactComponentName ,
189
- exportType ,
190
- interf
191
- ) ;
192
- }
193
- } else if ( ! exportType ) {
194
- createFunctionalComponent ( m , componentName , propTypes , interf ) ;
210
+ createClassOrExportedClass (
211
+ m ,
212
+ componentName ,
213
+ reactComponentName ,
214
+ exportType ,
215
+ interf
216
+ ) ;
195
217
} else {
218
+ createFunctionalOrExportedFunctionalComponent (
219
+ m ,
220
+ componentName ,
221
+ propTypes ,
222
+ exportType ! ,
223
+ intersection ,
224
+ interf
225
+ ) ;
226
+ }
227
+ }
228
+ function createClassOrExportedClass (
229
+ m : dom . ModuleDeclaration ,
230
+ componentName : string ,
231
+ reactComponentName : string | undefined ,
232
+ exportType : dom . DeclarationFlags | undefined ,
233
+ interf : dom . InterfaceDeclaration
234
+ ) : void {
235
+ if ( exportType ) {
236
+ createExportedClassComponent (
237
+ m ,
238
+ componentName ,
239
+ reactComponentName ,
240
+ exportType ,
241
+ interf
242
+ ) ;
243
+ } else {
244
+ createClassComponent ( m , componentName , reactComponentName , interf ) ;
245
+ }
246
+ }
247
+ function createFunctionalOrExportedFunctionalComponent (
248
+ m : dom . ModuleDeclaration ,
249
+ componentName : string ,
250
+ propTypes : any ,
251
+ exportType : dom . DeclarationFlags | undefined ,
252
+ intersection : any ,
253
+ interf : dom . InterfaceDeclaration
254
+ ) : void {
255
+ if ( exportType ) {
196
256
createExportedFunctionalComponent (
197
257
m ,
198
258
componentName ,
199
259
propTypes ,
200
260
exportType ,
261
+ intersection ,
262
+ interf
263
+ ) ;
264
+ } else {
265
+ createFunctionalComponent (
266
+ m ,
267
+ componentName ,
268
+ propTypes ,
269
+ intersection ,
201
270
interf
202
271
) ;
203
272
}
204
273
}
205
-
206
274
function createClassComponent (
207
275
m : dom . ModuleDeclaration ,
208
276
componentName : string ,
@@ -244,10 +312,13 @@ function createFunctionalComponent(
244
312
m : dom . ModuleDeclaration ,
245
313
componentName : string ,
246
314
propTypes : any ,
315
+ intersection : any ,
247
316
interf : dom . InterfaceDeclaration
248
317
) : dom . ConstDeclaration {
249
318
const typeDecl = dom . create . namedTypeReference (
250
- `React.FC${ propTypes ? `<${ interf . name } >` : '' } `
319
+ `React.FC${ propTypes ? `<${ interf . name } >` : '' } ${
320
+ intersection ? intersection : ''
321
+ } `
251
322
) ;
252
323
const constDecl = dom . create . const ( componentName , typeDecl ) ;
253
324
m . members . push ( constDecl ) ;
@@ -260,12 +331,14 @@ function createExportedFunctionalComponent(
260
331
componentName : string ,
261
332
propTypes : any ,
262
333
exportType : dom . DeclarationFlags ,
334
+ intersection : any ,
263
335
interf : dom . InterfaceDeclaration
264
336
) : void {
265
337
const constDecl = createFunctionalComponent (
266
338
m ,
267
339
componentName ,
268
340
propTypes ,
341
+ intersection ,
269
342
interf
270
343
) ;
271
344
if ( exportType === dom . DeclarationFlags . ExportDefault ) {
@@ -579,37 +652,107 @@ function getComponentNamesByJsxInBody(ast: AstQuery): string[] {
579
652
}
580
653
return [ ] ;
581
654
}
655
+ type ComponentProperties = {
656
+ name : string ;
657
+ properties : {
658
+ key : any ;
659
+ value : any ;
660
+ type ?: any ;
661
+ } [ ] ;
662
+ } ;
582
663
583
664
function getComponentNamesByObject (
584
665
ast : AstQuery ,
585
666
componentNames : string [ ]
586
- ) : { name : string ; properties : object | undefined } [ ] {
587
- const res = ast . query ( `
588
- /:program *
589
- / VariableDeclaration
590
- / VariableDeclarator[
591
- /:init ObjectExpression
592
- // ObjectProperty
593
- ],
594
- /:program *
595
- / ExportNamedDeclaration
596
- // VariableDeclarator[
597
- /:init ObjectExpression
598
- // ObjectProperty
667
+ ) : ComponentProperties [ ] {
668
+ let arr : ComponentProperties [ ] = [ ] ;
669
+ componentNames . forEach ( ( name ) => {
670
+ const res = ast . query ( `
671
+ /:program *
672
+ / VariableDeclaration
673
+ / VariableDeclarator[
674
+ /:init ObjectExpression
675
+ // ObjectProperty
676
+ /:value Identifier[@name == '${ name } ']
677
+ ],
678
+ /:program *
679
+ / ExportNamedDeclaration
680
+ // VariableDeclarator[
681
+ /:init ObjectExpression
682
+ // ObjectProperty
683
+ /:value Identifier[@name == '${ name } ']
684
+ ],
685
+ /:program *
686
+ / ExportDefaultDeclaration [
687
+ // ObjectProperty
688
+ /:value Identifier[@name == '${ name } ']
689
+ ] /:declaration ObjectExpression
690
+ ` ) ;
691
+
692
+ if ( res . length > 0 ) {
693
+ const matches : ComponentProperties [ ] = [ ] ;
694
+ // this accounts for export const X = {...} and export default {...}
695
+ // we need to give the default exported object a name hence '_default'
696
+ res . forEach ( ( match ) => {
697
+ if (
698
+ arr . findIndex (
699
+ ( val ) =>
700
+ val . name === match . id ?. name ||
701
+ ( val . name === '_default' && ! match . id ?. name )
702
+ ) === - 1
703
+ ) {
704
+ matches . push ( {
705
+ name : match . id ?. name || '_default' ,
706
+ properties : match . init ?. properties || match . properties ,
707
+ } ) ;
708
+ }
709
+ } ) ;
710
+
711
+ arr = [ ...arr , ...matches ] ;
712
+ }
713
+ } ) ;
714
+ return arr ;
715
+ }
716
+
717
+ function getComponentDotProperties (
718
+ ast : AstQuery ,
719
+ componentNames : string [ ]
720
+ ) : ComponentProperties [ ] {
721
+ let arr : ComponentProperties [ ] = [ ] ;
722
+ componentNames . forEach ( ( name ) => {
723
+ const res = ast . query ( `
724
+ /:program *
725
+ // AssignmentExpression[
726
+ /:left MemberExpression[
727
+ /:object Identifier[@name == '${ name } ']
599
728
]
600
- ` ) ;
601
- if ( res . length > 0 ) {
602
- return (
603
- res
604
- // only interested in components that exist
605
- . filter ( ( match ) => ! componentNames . includes ( match ) )
606
- . map ( ( match ) => ( {
607
- name : match . id ? match . id . name : '' ,
608
- properties : match . init ?. properties ,
609
- } ) )
610
- ) ;
611
- }
612
- return [ ] ;
729
+ &&
730
+ /:right Identifier
731
+ ]
732
+ ` ) ;
733
+ if ( res . length > 0 ) {
734
+ const properties : ComponentProperties [ 'properties' ] = [ ] ;
735
+ res . forEach ( ( match ) => {
736
+ if ( ! componentNames . includes ( match . right ?. name ) ) {
737
+ return ;
738
+ }
739
+ properties . push ( {
740
+ key : match . left ?. property ?. name ,
741
+ value : match . right ?. name ,
742
+ } ) ;
743
+ } ) ;
744
+ if ( properties . length > 0 ) {
745
+ arr = [
746
+ ...arr ,
747
+ {
748
+ name,
749
+ properties,
750
+ } ,
751
+ ] ;
752
+ }
753
+ }
754
+ } ) ;
755
+ return arr ;
613
756
}
614
757
615
758
function getPropTypes ( ast : AstQuery , componentName : string ) : any | undefined {
0 commit comments