Skip to content

Commit 49cb713

Browse files
authored
fix(react): infer constructor return type instead of void (#1037)
1 parent 2a106f3 commit 49cb713

3 files changed

Lines changed: 126 additions & 157 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@doc-kit/generator-react': patch
3+
---
4+
5+
Constructors now render their own class as the return type instead of `void`

packages/react/src/jsx-ast/utils/__tests__/signature.test.mjs

Lines changed: 106 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -6,239 +6,207 @@ import { generateSignature, getFullName } from '../signature.mjs';
66
describe('generateSignature', () => {
77
describe('function signatures', () => {
88
it('formats union return types without spaces as spaced', () => {
9-
const sig = generateSignature(
10-
'foo',
11-
{
12-
params: [],
13-
return: { type: 'string|number' },
14-
},
15-
''
16-
);
9+
const sig = generateSignature('foo', {
10+
params: [],
11+
return: { type: 'string|number' },
12+
});
1713

1814
assert.strictEqual(sig, 'foo(): string | number');
1915
});
2016

2117
it('preserves already spaced union return types', () => {
22-
const sig = generateSignature(
23-
'bar',
24-
{
25-
params: [],
26-
return: { type: 'Promise<string> | undefined' },
27-
},
28-
''
29-
);
18+
const sig = generateSignature('bar', {
19+
params: [],
20+
return: { type: 'Promise<string> | undefined' },
21+
});
3022

3123
assert.strictEqual(sig, 'bar(): Promise<string> | undefined');
3224
});
3325

3426
it('omits return type when undefined', () => {
35-
const sig = generateSignature(
36-
'baz',
37-
{
38-
params: [],
39-
return: undefined,
40-
},
41-
''
42-
);
27+
const sig = generateSignature('baz', {
28+
params: [],
29+
return: undefined,
30+
});
4331

4432
assert.strictEqual(sig, 'baz(): void');
4533
});
4634

4735
it('handles empty return type', () => {
36+
const sig = generateSignature('test', {
37+
params: [],
38+
return: null,
39+
});
40+
41+
assert.strictEqual(sig, 'test(): void');
42+
});
43+
44+
it('prefixes a constructor and infers its return type', () => {
4845
const sig = generateSignature(
49-
'test',
46+
'Constructor',
5047
{
5148
params: [],
52-
return: null,
49+
return: undefined,
5350
},
54-
''
51+
{ type: 'ctor' }
5552
);
5653

57-
assert.strictEqual(sig, 'test(): void');
54+
// Node's docs omit `Returns:` on constructors, but a constructor never
55+
// returns `void` — it yields an instance of its own class.
56+
assert.strictEqual(sig, 'new Constructor(): Constructor');
5857
});
5958

60-
it('includes prefix when provided', () => {
59+
it('keeps an explicit return type on a constructor', () => {
6160
const sig = generateSignature(
6261
'Constructor',
6362
{
6463
params: [],
65-
return: undefined,
64+
return: { type: 'net.Socket' },
6665
},
67-
'new '
66+
{ type: 'ctor' }
6867
);
6968

70-
assert.strictEqual(sig, 'new Constructor(): void');
69+
assert.strictEqual(sig, 'new Constructor(): net.Socket');
7170
});
7271

73-
it('handles complex union types with multiple pipes', () => {
72+
it('falls back when a return entry carries no type', () => {
7473
const sig = generateSignature(
75-
'complexFunc',
74+
'noType',
7675
{
7776
params: [],
78-
return: { type: 'string|number|boolean|null' },
77+
return: { type: undefined },
7978
},
80-
''
79+
{ type: 'method' }
8180
);
8281

82+
assert.strictEqual(sig, 'noType(): void');
83+
});
84+
85+
it('handles complex union types with multiple pipes', () => {
86+
const sig = generateSignature('complexFunc', {
87+
params: [],
88+
return: { type: 'string|number|boolean|null' },
89+
});
90+
8391
assert.strictEqual(
8492
sig,
8593
'complexFunc(): string | number | boolean | null'
8694
);
8795
});
8896

8997
it('filters empty parts in union types', () => {
90-
const sig = generateSignature(
91-
'filterFunc',
92-
{
93-
params: [],
94-
return: { type: 'string||number|' },
95-
},
96-
''
97-
);
98+
const sig = generateSignature('filterFunc', {
99+
params: [],
100+
return: { type: 'string||number|' },
101+
});
98102

99103
assert.strictEqual(sig, 'filterFunc(): string | number');
100104
});
101105
});
102106

103107
describe('parameters', () => {
104108
it('handles single parameter without optional flag or default', () => {
105-
const sig = generateSignature(
106-
'singleParam',
107-
{
108-
params: [{ name: 'value', optional: false }],
109-
return: undefined,
110-
},
111-
''
112-
);
109+
const sig = generateSignature('singleParam', {
110+
params: [{ name: 'value', optional: false }],
111+
return: undefined,
112+
});
113113

114114
assert.strictEqual(sig, 'singleParam(value): void');
115115
});
116116

117117
it('handles multiple parameters', () => {
118-
const sig = generateSignature(
119-
'multiParam',
120-
{
121-
params: [
122-
{ name: 'first', optional: false },
123-
{ name: 'second', optional: false },
124-
],
125-
return: undefined,
126-
},
127-
''
128-
);
118+
const sig = generateSignature('multiParam', {
119+
params: [
120+
{ name: 'first', optional: false },
121+
{ name: 'second', optional: false },
122+
],
123+
return: undefined,
124+
});
129125

130126
assert.strictEqual(sig, 'multiParam(first, second): void');
131127
});
132128

133129
it('marks optional parameters with question mark', () => {
134-
const sig = generateSignature(
135-
'optionalParam',
136-
{
137-
params: [
138-
{ name: 'required', optional: false },
139-
{ name: 'optional', optional: true },
140-
],
141-
return: undefined,
142-
},
143-
''
144-
);
130+
const sig = generateSignature('optionalParam', {
131+
params: [
132+
{ name: 'required', optional: false },
133+
{ name: 'optional', optional: true },
134+
],
135+
return: undefined,
136+
});
145137

146138
assert.strictEqual(sig, 'optionalParam(required, optional?): void');
147139
});
148140

149141
it('marks parameters with defaults as optional', () => {
150-
const sig = generateSignature(
151-
'defaultParam',
152-
{
153-
params: [
154-
{ name: 'normal', optional: false },
155-
{ name: 'withDefault', optional: false, default: 'defaultValue' },
156-
],
157-
return: undefined,
158-
},
159-
''
160-
);
142+
const sig = generateSignature('defaultParam', {
143+
params: [
144+
{ name: 'normal', optional: false },
145+
{ name: 'withDefault', optional: false, default: 'defaultValue' },
146+
],
147+
return: undefined,
148+
});
161149

162150
assert.strictEqual(sig, 'defaultParam(normal, withDefault?): void');
163151
});
164152

165153
it('handles parameters that are both optional and have defaults', () => {
166-
const sig = generateSignature(
167-
'bothOptionalAndDefault',
168-
{
169-
params: [{ name: 'param', optional: true, default: 'value' }],
170-
return: undefined,
171-
},
172-
''
173-
);
154+
const sig = generateSignature('bothOptionalAndDefault', {
155+
params: [{ name: 'param', optional: true, default: 'value' }],
156+
return: undefined,
157+
});
174158

175159
assert.strictEqual(sig, 'bothOptionalAndDefault(param?): void');
176160
});
177161

178162
it('handles empty params array', () => {
179-
const sig = generateSignature(
180-
'noParams',
181-
{
182-
params: [],
183-
return: { type: 'string' },
184-
},
185-
''
186-
);
163+
const sig = generateSignature('noParams', {
164+
params: [],
165+
return: { type: 'string' },
166+
});
187167

188168
assert.strictEqual(sig, 'noParams(): string');
189169
});
190170

191171
it('handles params without optional property', () => {
192-
const sig = generateSignature(
193-
'implicitOptional',
194-
{
195-
params: [{ name: 'param1' }, { name: 'param2', default: 'value' }],
196-
return: undefined,
197-
},
198-
''
199-
);
172+
const sig = generateSignature('implicitOptional', {
173+
params: [{ name: 'param1' }, { name: 'param2', default: 'value' }],
174+
return: undefined,
175+
});
200176

201177
assert.strictEqual(sig, 'implicitOptional(param1, param2?): void');
202178
});
203179
});
204180

205181
describe('class signatures', () => {
206182
it('generates class signature with extends clause', () => {
207-
const sig = generateSignature(
208-
'MyClass',
209-
{
210-
params: [],
211-
extends: { type: 'BaseClass' },
212-
},
213-
''
214-
);
183+
const sig = generateSignature('MyClass', {
184+
params: [],
185+
extends: { type: 'BaseClass' },
186+
});
215187

216188
assert.strictEqual(sig, 'class MyClass extends BaseClass');
217189
});
218190

219-
it('generates class signature with extends and prefix', () => {
191+
it('does not prefix a class signature', () => {
220192
const sig = generateSignature(
221193
'MyClass',
222194
{
223195
params: [],
224196
extends: { type: 'BaseClass' },
225197
},
226-
'abstract '
198+
{ type: 'class' }
227199
);
228200

229-
assert.strictEqual(sig, 'class abstract MyClass extends BaseClass');
201+
assert.strictEqual(sig, 'class MyClass extends BaseClass');
230202
});
231203

232204
it('ignores params and return type for class with extends', () => {
233-
const sig = generateSignature(
234-
'MyClass',
235-
{
236-
params: [{ name: 'ignored', optional: false }],
237-
return: { type: 'ignored' },
238-
extends: { type: 'BaseClass' },
239-
},
240-
''
241-
);
205+
const sig = generateSignature('MyClass', {
206+
params: [{ name: 'ignored', optional: false }],
207+
return: { type: 'ignored' },
208+
extends: { type: 'BaseClass' },
209+
});
242210

243211
assert.strictEqual(sig, 'class MyClass extends BaseClass');
244212
});
@@ -250,7 +218,7 @@ describe('generateSignature', () => {
250218
params: [{ name: 'param', optional: false }],
251219
return: { type: 'MyClass' },
252220
},
253-
'new '
221+
{ type: 'ctor' }
254222
);
255223

256224
assert.strictEqual(sig, 'new MyClass(param): MyClass');
@@ -259,27 +227,19 @@ describe('generateSignature', () => {
259227

260228
describe('edge cases', () => {
261229
it('handles null return type', () => {
262-
const sig = generateSignature(
263-
'nullReturn',
264-
{
265-
params: [],
266-
return: null,
267-
},
268-
''
269-
);
230+
const sig = generateSignature('nullReturn', {
231+
params: [],
232+
return: null,
233+
});
270234

271235
assert.strictEqual(sig, 'nullReturn(): void');
272236
});
273237

274238
it('handles missing extends property', () => {
275-
const sig = generateSignature(
276-
'NoExtends',
277-
{
278-
params: [{ name: 'param' }],
279-
return: { type: 'void' },
280-
},
281-
''
282-
);
239+
const sig = generateSignature('NoExtends', {
240+
params: [{ name: 'param' }],
241+
return: { type: 'void' },
242+
});
283243

284244
assert.strictEqual(sig, 'NoExtends(param): void');
285245
});

0 commit comments

Comments
 (0)