11import  *  as  ts  from  'typescript' ; 
2+ import  *  as  _  from  'lodash' ; 
23
34import  *  as  helpers  from  '../helpers' ; 
45
@@ -13,8 +14,8 @@ import * as helpers from '../helpers';
1314 * type Foo = {foo: string; bar: number;} 
1415 */ 
1516export  function  collapseIntersectionInterfacesTransformFactoryFactory ( 
16-          typeChecker : ts . TypeChecker , 
17-      ) : ts . TransformerFactory < ts . SourceFile >  { 
17+     typeChecker : ts . TypeChecker , 
18+ ) : ts . TransformerFactory < ts . SourceFile >  { 
1819    return  function  collapseIntersectionInterfacesTransformFactory ( context : ts . TransformationContext )  { 
1920        return  function  collapseIntersectionInterfacesTransform ( sourceFile : ts . SourceFile )  { 
2021            const  visited  =  ts . visitEachChild ( sourceFile ,  visitor ,  context ) ; 
@@ -31,28 +32,121 @@ export function collapseIntersectionInterfacesTransformFactoryFactory(
3132            } 
3233
3334            function  visitTypeAliasDeclaration ( node : ts . TypeAliasDeclaration )  { 
34-                 if  ( 
35-                     ts . isIntersectionTypeNode ( node . type ) 
36-                     &&  node . type . types . every ( ts . isTypeLiteralNode ) 
37-                 )  { 
38-                     // We need cast `node.type.types` to `ts.NodeArray<ts.TypeLiteralNode>` 
39-                     // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` 
40-                     const  allMembers  =  ( node . type . types  as  ts . NodeArray < ts . TypeLiteralNode > ) 
41-                         . map ( ( type )  =>  type . members ) 
42-                         . reduce ( ( all ,  members )  =>  ts . createNodeArray ( all . concat ( members ) ) ,  ts . createNodeArray ( [ ] ) ) ; 
43- 
35+                 if  ( ts . isIntersectionTypeNode ( node . type ) )  { 
4436                    return  ts . createTypeAliasDeclaration ( 
4537                        [ ] , 
4638                        [ ] , 
4739                        node . name . text , 
4840                        [ ] , 
49-                         ts . createTypeLiteralNode ( allMembers ) , 
41+                         visitIntersectionTypeNode ( node . type ) , 
5042                    ) ; 
5143                } 
5244
5345                return  node ; 
5446            } 
55-         } 
56-     } 
57- } 
5847
48+             function  visitIntersectionTypeNode ( node : ts . IntersectionTypeNode )  { 
49+                 // Only intersection of type literals can be colapsed. 
50+                 // We are currently ignoring intersections such as `{foo: string} & {bar: string} & TypeRef` 
51+                 // TODO: handle mix of type references and multiple literal types 
52+                 if  ( ! node . types . every ( typeNode  =>  ts . isTypeLiteralNode ( typeNode ) ) )  { 
53+                     return  node ; 
54+                 } 
55+ 
56+                 // We need cast `node.type.types` to `ts.NodeArray<ts.TypeLiteralNode>` 
57+                 // because TypeScript can't figure out `node.type.types.every(ts.isTypeLiteralNode)` 
58+                 const  types  =  node . types  as  ts . NodeArray < ts . TypeLiteralNode > ; 
59+ 
60+                 // Build a map of member names to all of types found in intersectioning type literals 
61+                 // For instance {foo: string, bar: number} & { foo: number } will result in a map like this: 
62+                 // Map { 
63+                 //   'foo' => Set { 'string', 'number' }, 
64+                 //   'bar' => Set { 'number' } 
65+                 // } 
66+                 const  membersMap  =  new  Map < string  |  symbol ,  Set < ts . TypeNode > > ( ) ; 
67+ 
68+                 // A sepecial member of type literal nodes is index signitures which don't have a name 
69+                 // We use this symbol to track it in our members map 
70+                 const  INDEX_SIGNITUTRE_MEMBER  =  Symbol ( 'Index signiture member' ) ; 
71+ 
72+                 // Keep a reference of first index signiture member parameters. (ignore rest) 
73+                 let  indexMemberParameter : ts . NodeArray < ts . ParameterDeclaration >  |  null  =  null ; 
74+ 
75+                 // Iterate through all of type literal nodes members and add them to the members map 
76+                 types . forEach ( typeNode  =>  { 
77+                     typeNode . members . forEach ( member  =>  { 
78+                         if  ( ts . isIndexSignatureDeclaration ( member ) )  { 
79+                             if  ( member . type  !==  undefined )  { 
80+                                 if  ( membersMap . has ( INDEX_SIGNITUTRE_MEMBER ) )  { 
81+                                     membersMap . get ( INDEX_SIGNITUTRE_MEMBER ) ! . add ( member . type ) ; 
82+                                 }  else  { 
83+                                     indexMemberParameter  =  member . parameters ; 
84+                                     membersMap . set ( INDEX_SIGNITUTRE_MEMBER ,  new  Set ( [ member . type ] ) ) ; 
85+                                 } 
86+                             } 
87+                         }  else  if  ( ts . isPropertySignature ( member ) )  { 
88+                             if  ( member . type  !==  undefined )  { 
89+                                 let  memberName  =  member . name . getText ( sourceFile ) ; 
90+ 
91+                                 // For unknown reasons, member.name.getText() is returning nothing in some cases 
92+                                 // This is probably because previous transformers did something with the AST that 
93+                                 // index of text string of member identifier is lost 
94+                                 // TODO: investigate 
95+                                 if  ( ! memberName )  { 
96+                                     memberName  =  ( member . name  as  any ) . escapedText ; 
97+                                 } 
98+ 
99+                                 if  ( membersMap . has ( memberName ) )  { 
100+                                     membersMap . get ( memberName ) ! . add ( member . type ) ; 
101+                                 }  else  { 
102+                                     membersMap . set ( memberName ,  new  Set ( [ member . type ] ) ) ; 
103+                                 } 
104+                             } 
105+                         } 
106+                     } ) ; 
107+                 } ) ; 
108+ 
109+                 // Result type literal members list 
110+                 const  finalMembers : Array < ts . PropertySignature  |  ts . IndexSignatureDeclaration >  =  [ ] ; 
111+ 
112+                 // Put together the map into a type literal that has member per each map entery and type of that 
113+                 // member is a union of all types in vlues for that member name in members map 
114+                 // if a member has only one type, create a simple type literal for it 
115+                 for  ( const  [ name ,  types ]  of  membersMap . entries ( ) )  { 
116+                     if  ( typeof  name  ===  'symbol' )  { 
117+                         continue ; 
118+                     } 
119+                     // if for this name there is only one type found use the first type, otherwise make a union of all types 
120+                     let  resultType  =  types . size  ===  1  ? Array . from ( types ) [ 0 ]  : createUnionType ( Array . from ( types ) ) ; 
121+ 
122+                     finalMembers . push ( ts . createPropertySignature ( [ ] ,  name ,  undefined ,  resultType ,  undefined ) ) ; 
123+                 } 
124+ 
125+                 // Handle index signiture member 
126+                 if  ( membersMap . has ( INDEX_SIGNITUTRE_MEMBER ) )  { 
127+                     const  indexTypes  =  Array . from ( membersMap . get ( INDEX_SIGNITUTRE_MEMBER ) ! ) ; 
128+                     let  indexType  =  indexTypes [ 0 ] ; 
129+                     if  ( indexTypes . length  >  1 )  { 
130+                         indexType  =  createUnionType ( indexTypes ) ; 
131+                     } 
132+                     const  indexSigniture  =  ts . createIndexSignature ( [ ] ,  [ ] ,  indexMemberParameter ! ,  indexType ) ; 
133+                     finalMembers . push ( indexSigniture ) ; 
134+                 } 
135+ 
136+                 // Generate one single type literal node 
137+                 return  ts . createTypeLiteralNode ( finalMembers ) ; 
138+             } 
139+ 
140+             /** 
141+              * Create a union type from multiple type nodes 
142+              * @param  types 
143+              */ 
144+             function  createUnionType ( types : ts . TypeNode [ ] )  { 
145+                 // first dedupe literal types 
146+                 // TODO: this only works if all types are primitive types like string or number 
147+                 const  uniqueTypes  =  _ . uniqBy ( types ,  type  =>  type . kind ) ; 
148+                 return  ts . createUnionOrIntersectionTypeNode ( ts . SyntaxKind . UnionType ,  uniqueTypes ) ; 
149+             } 
150+         } ; 
151+     } ; 
152+ } 
0 commit comments