Skip to content

Commit 5d3470d

Browse files
committed
Better handle identifiers types as type parameters
1 parent 6748f09 commit 5d3470d

File tree

5 files changed

+268
-13
lines changed

5 files changed

+268
-13
lines changed

src/parsers/propertyParser.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ export function parseProperty(
3737
parsedType = new IntrinsicNode('any');
3838
isOptional = Boolean(propertySignature.questionToken);
3939
} else {
40-
parsedType = resolveType(type, context, propertySignature?.type, skipResolvingComplexTypes);
40+
parsedType = resolveType(
41+
type,
42+
context,
43+
isTypeParameterLike(type) ? undefined : propertySignature?.type,
44+
skipResolvingComplexTypes,
45+
);
4146
isOptional = Boolean(propertySymbol.flags & ts.SymbolFlags.Optional);
4247
}
4348

@@ -59,3 +64,25 @@ export function parseProperty(
5964
parsedSymbolStack.pop();
6065
}
6166
}
67+
68+
function isTypeParameterLike(type: ts.Type): boolean {
69+
// Check if the type is a type parameter
70+
return (
71+
(type.flags & ts.TypeFlags.TypeParameter) !== 0 ||
72+
((type.flags & ts.TypeFlags.Union) !== 0 && isOptionalTypeParameter(type as ts.UnionType))
73+
);
74+
}
75+
76+
function isOptionalTypeParameter(type: ts.UnionType): boolean {
77+
// Check if the type is defined as
78+
// foo?: T
79+
// where T is a type parameter
80+
81+
return (
82+
type.types.length === 2 &&
83+
type.types.some((t) => t.flags & ts.TypeFlags.Undefined) &&
84+
type.types.some(
85+
(t) => 'objectFlags' in t && ((t.objectFlags as number) & ts.ObjectFlags.Instantiated) !== 0,
86+
)
87+
);
88+
}

test/generics-closed/input.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
type ComponentProps<TState, RenderFunctionProps = GenericHTMLProps> = {
2+
className?: string | ((state: TState) => string);
3+
render?: ComponentRenderFn<RenderFunctionProps, TState>;
4+
};
5+
6+
export function fn(props: MyComponent.Props) {}
7+
8+
type ComponentRenderFn<Props, State> = (props: Props, state: State) => React.ReactElement<unknown>;
9+
10+
export type GenericHTMLProps = React.HTMLAttributes<any> & { ref?: React.Ref<any> | undefined };
11+
12+
namespace MyComponent {
13+
export interface State {
14+
disabled: boolean;
15+
}
16+
17+
export interface Props extends ComponentProps<State, GenericHTMLProps> {}
18+
}

test/generics-closed/output.json

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
{
2+
"name": "test/generics-closed/input",
3+
"exports": [
4+
{
5+
"name": "fn",
6+
"type": {
7+
"kind": "function",
8+
"name": "fn",
9+
"parentNamespaces": [],
10+
"callSignatures": [
11+
{
12+
"parameters": [
13+
{
14+
"type": {
15+
"kind": "object",
16+
"name": "Props",
17+
"parentNamespaces": [
18+
"MyComponent"
19+
],
20+
"properties": [
21+
{
22+
"name": "className",
23+
"type": {
24+
"kind": "union",
25+
"types": [
26+
{
27+
"kind": "intrinsic",
28+
"parentNamespaces": [],
29+
"name": "string"
30+
},
31+
{
32+
"kind": "function",
33+
"parentNamespaces": [],
34+
"callSignatures": [
35+
{
36+
"parameters": [
37+
{
38+
"type": {
39+
"kind": "object",
40+
"name": "State",
41+
"parentNamespaces": [
42+
"MyComponent"
43+
],
44+
"properties": [
45+
{
46+
"name": "disabled",
47+
"type": {
48+
"kind": "intrinsic",
49+
"parentNamespaces": [],
50+
"name": "boolean"
51+
},
52+
"optional": false
53+
}
54+
]
55+
},
56+
"name": "state",
57+
"optional": false
58+
}
59+
],
60+
"returnValueType": {
61+
"kind": "intrinsic",
62+
"parentNamespaces": [],
63+
"name": "string"
64+
}
65+
}
66+
]
67+
},
68+
{
69+
"kind": "intrinsic",
70+
"parentNamespaces": [],
71+
"name": "undefined"
72+
}
73+
],
74+
"parentNamespaces": []
75+
},
76+
"optional": true
77+
},
78+
{
79+
"name": "render",
80+
"type": {
81+
"kind": "union",
82+
"types": [
83+
{
84+
"kind": "function",
85+
"name": "ComponentRenderFn",
86+
"parentNamespaces": [],
87+
"callSignatures": [
88+
{
89+
"parameters": [
90+
{
91+
"type": {
92+
"kind": "intersection",
93+
"types": [
94+
{
95+
"kind": "reference",
96+
"name": "HTMLAttributes<any>",
97+
"parentNamespaces": [
98+
"React"
99+
]
100+
},
101+
{
102+
"kind": "object",
103+
"parentNamespaces": [],
104+
"properties": [
105+
{
106+
"name": "ref",
107+
"type": {
108+
"kind": "union",
109+
"types": [
110+
{
111+
"kind": "reference",
112+
"name": "Ref<any>",
113+
"parentNamespaces": [
114+
"React"
115+
]
116+
},
117+
{
118+
"kind": "intrinsic",
119+
"parentNamespaces": [],
120+
"name": "undefined"
121+
}
122+
],
123+
"parentNamespaces": []
124+
},
125+
"optional": true
126+
}
127+
]
128+
}
129+
],
130+
"name": "Props",
131+
"parentNamespaces": [],
132+
"properties": [
133+
{
134+
"name": "ref",
135+
"type": {
136+
"kind": "union",
137+
"types": [
138+
{
139+
"kind": "reference",
140+
"name": "Ref<any>",
141+
"parentNamespaces": [
142+
"React"
143+
]
144+
},
145+
{
146+
"kind": "intrinsic",
147+
"parentNamespaces": [],
148+
"name": "undefined"
149+
}
150+
],
151+
"parentNamespaces": []
152+
},
153+
"optional": true
154+
}
155+
]
156+
},
157+
"name": "props",
158+
"optional": false
159+
},
160+
{
161+
"type": {
162+
"kind": "object",
163+
"name": "State",
164+
"parentNamespaces": [
165+
"MyComponent"
166+
],
167+
"properties": [
168+
{
169+
"name": "disabled",
170+
"type": {
171+
"kind": "intrinsic",
172+
"parentNamespaces": [],
173+
"name": "boolean"
174+
},
175+
"optional": false
176+
}
177+
]
178+
},
179+
"name": "state",
180+
"optional": false
181+
}
182+
],
183+
"returnValueType": {
184+
"kind": "reference",
185+
"name": "ReactElement<unknown, string | JSXElementConstructor<any>>",
186+
"parentNamespaces": [
187+
"React"
188+
]
189+
}
190+
}
191+
]
192+
},
193+
{
194+
"kind": "intrinsic",
195+
"parentNamespaces": [],
196+
"name": "undefined"
197+
}
198+
],
199+
"parentNamespaces": []
200+
},
201+
"optional": true
202+
}
203+
]
204+
},
205+
"name": "props",
206+
"optional": false
207+
}
208+
],
209+
"returnValueType": {
210+
"kind": "intrinsic",
211+
"parentNamespaces": [],
212+
"name": "void"
213+
}
214+
}
215+
]
216+
}
217+
}
218+
]
219+
}

test/react-event-handlers/output.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@
3737
"name": "undefined"
3838
}
3939
],
40-
"name": "KeyboardEventHandler",
41-
"parentNamespaces": [
42-
"React"
43-
]
40+
"parentNamespaces": []
4441
},
4542
"optional": true
4643
},
@@ -73,10 +70,7 @@
7370
"name": "undefined"
7471
}
7572
],
76-
"name": "FocusEventHandler",
77-
"parentNamespaces": [
78-
"React"
79-
]
73+
"parentNamespaces": []
8074
},
8175
"optional": true
8276
}

test/react-refs/output.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,7 @@
8282
"name": "undefined"
8383
}
8484
],
85-
"name": "RefCallback",
86-
"parentNamespaces": [
87-
"React"
88-
]
85+
"parentNamespaces": []
8986
},
9087
"optional": true
9188
},

0 commit comments

Comments
 (0)