@@ -73,10 +73,12 @@ export function createTypings(
7373 }
7474 const alreadyDefined : string [ ] = [ ] ;
7575
76+ const componentDots = getComponentDotProperties ( ast , componentNames ) ;
7677 componentNames . forEach ( ( componentName ) => {
7778 const exportType = getComponentExportType ( ast , componentName ) ;
7879 const propTypes = getPropTypes ( ast , componentName ) ;
79- if ( exportType ) {
80+ const intersection = getIntersection ( componentDots , componentName ) ;
81+ if ( exportType || componentDots . length ) {
8082 alreadyDefined . push ( componentName ) ;
8183 createExportedTypes (
8284 m ,
@@ -86,6 +88,7 @@ export function createTypings(
8688 propTypes ,
8789 importedPropTypes ,
8890 exportType ,
91+ intersection ,
8992 options
9093 ) ;
9194 }
@@ -94,49 +97,56 @@ export function createTypings(
9497 // top level object variables
9598 const componentObject = getComponentNamesByObject ( ast , componentNames ) ;
9699
97- componentObject . forEach ( ( { name, properties = { } } ) => {
100+ componentObject . forEach ( ( { name, properties } ) => {
98101 const obj = dom . create . objectType ( [ ] ) ;
99102 let hasType ;
100103
101104 Object . keys ( properties ) . forEach ( ( k ) => {
102105 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+ ) ;
130129 }
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+ }
132139 } ) ;
133140 if ( hasType ) {
134141 const exportType = getComponentExportType ( ast , name ) ;
135142
136143 const objConst = dom . create . const ( name , obj ) ;
137144 m . members . push ( objConst ) ;
138145
139- if ( exportType === dom . DeclarationFlags . ExportDefault ) {
146+ if (
147+ exportType === dom . DeclarationFlags . ExportDefault ||
148+ name === '_default'
149+ ) {
140150 m . members . push ( dom . create . exportDefault ( name ) ) ;
141151 } else {
142152 objConst . flags = exportType ;
@@ -150,6 +160,24 @@ export function createTypings(
150160 return dom . emit ( m , { tripleSlashDirectives } ) ;
151161 }
152162}
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+ }
153181
154182function createExportedTypes (
155183 m : dom . ModuleDeclaration ,
@@ -159,6 +187,7 @@ function createExportedTypes(
159187 propTypes : any ,
160188 importedPropTypes : ImportedPropTypes ,
161189 exportType : dom . DeclarationFlags | undefined ,
190+ intersection : any ,
162191 options : IOptions
163192) : void {
164193 const classComponent = isClassComponent (
@@ -177,32 +206,71 @@ function createExportedTypes(
177206 if ( propTypes || classComponent ) {
178207 m . members . push ( interf ) ;
179208 }
180-
181209 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+ ) ;
195217 } 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 ) {
196256 createExportedFunctionalComponent (
197257 m ,
198258 componentName ,
199259 propTypes ,
200260 exportType ,
261+ intersection ,
262+ interf
263+ ) ;
264+ } else {
265+ createFunctionalComponent (
266+ m ,
267+ componentName ,
268+ propTypes ,
269+ intersection ,
201270 interf
202271 ) ;
203272 }
204273}
205-
206274function createClassComponent (
207275 m : dom . ModuleDeclaration ,
208276 componentName : string ,
@@ -244,10 +312,13 @@ function createFunctionalComponent(
244312 m : dom . ModuleDeclaration ,
245313 componentName : string ,
246314 propTypes : any ,
315+ intersection : any ,
247316 interf : dom . InterfaceDeclaration
248317) : dom . ConstDeclaration {
249318 const typeDecl = dom . create . namedTypeReference (
250- `React.FC${ propTypes ? `<${ interf . name } >` : '' } `
319+ `React.FC${ propTypes ? `<${ interf . name } >` : '' } ${
320+ intersection ? intersection : ''
321+ } `
251322 ) ;
252323 const constDecl = dom . create . const ( componentName , typeDecl ) ;
253324 m . members . push ( constDecl ) ;
@@ -260,12 +331,14 @@ function createExportedFunctionalComponent(
260331 componentName : string ,
261332 propTypes : any ,
262333 exportType : dom . DeclarationFlags ,
334+ intersection : any ,
263335 interf : dom . InterfaceDeclaration
264336) : void {
265337 const constDecl = createFunctionalComponent (
266338 m ,
267339 componentName ,
268340 propTypes ,
341+ intersection ,
269342 interf
270343 ) ;
271344 if ( exportType === dom . DeclarationFlags . ExportDefault ) {
@@ -579,37 +652,107 @@ function getComponentNamesByJsxInBody(ast: AstQuery): string[] {
579652 }
580653 return [ ] ;
581654}
655+ type ComponentProperties = {
656+ name : string ;
657+ properties : {
658+ key : any ;
659+ value : any ;
660+ type ?: any ;
661+ } [ ] ;
662+ } ;
582663
583664function getComponentNamesByObject (
584665 ast : AstQuery ,
585666 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 } ']
599728 ]
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 ;
613756}
614757
615758function getPropTypes ( ast : AstQuery , componentName : string ) : any | undefined {
0 commit comments