@@ -53,26 +53,13 @@ function hasReactNodeLikeReturnType(type: FunctionNode) {
5353
5454function squashComponentProps ( callSignatures : CallSignature [ ] , context : ParserContext ) {
5555 // squash props
56- // { variant: 'a', href: string } & { variant: 'b' }
56+ // { variant: 'a', href: string } | { variant: 'b' }
5757 // to
5858 // { variant: 'a' | 'b', href?: string }
59- const props : Record < string , PropertyNode > = { } ;
59+ const props : Map < string , PropertyNode > = new Map < string , PropertyNode > ( ) ;
6060 const usedPropsPerSignature : Set < string > [ ] = [ ] ;
6161
62- function unwrapUnionType ( type : UnionNode ) : ObjectNode [ ] {
63- return type . types
64- . map ( ( type ) => {
65- if ( type instanceof ObjectNode ) {
66- return type ;
67- } else if ( type instanceof UnionNode ) {
68- return unwrapUnionType ( type ) ;
69- }
70- } )
71- . flat ( )
72- . filter ( ( t ) => ! ! t ) ;
73- }
74-
75- const allParametersUnionMembers = callSignatures
62+ const propsFromCallSignatures = callSignatures
7663 . map ( ( signature ) => {
7764 const propsParameter = signature . parameters [ 0 ] ;
7865 if ( ! propsParameter ) {
@@ -94,44 +81,61 @@ function squashComponentProps(callSignatures: CallSignature[], context: ParserCo
9481 . flat ( )
9582 . filter ( ( t ) => ! ! t ) ;
9683
97- allParametersUnionMembers . forEach ( ( propUnionMember ) => {
84+ propsFromCallSignatures . forEach ( ( propsObject ) => {
9885 const usedProps : Set < string > = new Set ( ) ;
9986
100- propUnionMember . properties . forEach ( ( propNode ) => {
87+ propsObject . properties . forEach ( ( propNode ) => {
10188 usedProps . add ( propNode . name ) ;
10289
103- let { [ propNode . name ] : currentTypeNode } = props ;
104- if ( currentTypeNode === undefined ) {
105- currentTypeNode = propNode ;
106- } else if ( currentTypeNode . $$id !== propNode . $$id ) {
107- const mergedPropType = new UnionNode ( undefined , [ ] , [ currentTypeNode . type , propNode . type ] ) ;
108-
109- currentTypeNode = new PropertyNode (
110- currentTypeNode . name ,
90+ // Check if a prop with a given name has already been encountered.
91+ const existingPropNode = props . get ( propNode . name ) ;
92+ if ( existingPropNode === undefined ) {
93+ // If not, we can just add it.
94+ props . set ( propNode . name , propNode ) ;
95+ } else {
96+ // If it has, we need to merge the types in a union.
97+ // If both prop objects define the prop with the same type, the UnionNode constructor will deduplicate them.
98+ const mergedPropType = new UnionNode ( undefined , [ ] , [ existingPropNode . type , propNode . type ] ) ;
99+
100+ // If the current prop is optional, the whole union will be optional.
101+ const mergedPropNode = new PropertyNode (
102+ existingPropNode . name ,
111103 mergedPropType . types . length === 1 ? mergedPropType . types [ 0 ] : mergedPropType ,
112- currentTypeNode . documentation ,
113- currentTypeNode . optional || propNode . optional ,
114- undefined ,
104+ existingPropNode . documentation ,
105+ existingPropNode . optional || propNode . optional ,
115106 ) ;
116- }
117107
118- props [ propNode . name ] = currentTypeNode ;
108+ props . set ( propNode . name , mergedPropNode ) ;
109+ }
119110 } ) ;
120111
121112 usedPropsPerSignature . push ( usedProps ) ;
122113 } ) ;
123114
124- return Object . entries ( props ) . map ( ( [ name , property ] ) => {
115+ // If a prop is used in some signatures, but not in others, we need to mark it as optional.
116+ return [ ...props . entries ( ) ] . map ( ( [ name , property ] ) => {
125117 const onlyUsedInSomeSignatures = usedPropsPerSignature . some ( ( props ) => ! props . has ( name ) ) ;
126118 if ( onlyUsedInSomeSignatures ) {
127- // mark as optional
128119 return markPropertyAsOptional ( property , context ) ;
129120 }
130121
131122 return property ;
132123 } ) ;
133124}
134125
126+ function unwrapUnionType ( type : UnionNode ) : ObjectNode [ ] {
127+ return type . types
128+ . map ( ( type ) => {
129+ if ( type instanceof ObjectNode ) {
130+ return type ;
131+ } else if ( type instanceof UnionNode ) {
132+ return unwrapUnionType ( type ) ;
133+ }
134+ } )
135+ . flat ( )
136+ . filter ( ( t ) => ! ! t ) ;
137+ }
138+
135139function markPropertyAsOptional ( property : PropertyNode , context : ParserContext ) {
136140 const canBeUndefined =
137141 property . type instanceof UnionNode &&
@@ -142,8 +146,8 @@ function markPropertyAsOptional(property: PropertyNode, context: ParserContext)
142146 const { compilerOptions } = context ;
143147 if ( ! canBeUndefined && ! compilerOptions . exactOptionalPropertyTypes ) {
144148 const newType = new UnionNode ( undefined , [ ] , [ property . type , new IntrinsicNode ( 'undefined' ) ] ) ;
145- return new PropertyNode ( property . name , newType , property . documentation , true , undefined ) ;
149+ return new PropertyNode ( property . name , newType , property . documentation , true ) ;
146150 }
147151
148- return new PropertyNode ( property . name , property . type , property . documentation , true , undefined ) ;
152+ return new PropertyNode ( property . name , property . type , property . documentation , true ) ;
149153}
0 commit comments