Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/constructor-return-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@doc-kit/generator-react': patch
---

Constructors now render their own class as the return type instead of `void`
252 changes: 106 additions & 146 deletions packages/react/src/jsx-ast/utils/__tests__/signature.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,239 +6,207 @@ import { generateSignature, getFullName } from '../signature.mjs';
describe('generateSignature', () => {
describe('function signatures', () => {
it('formats union return types without spaces as spaced', () => {
const sig = generateSignature(
'foo',
{
params: [],
return: { type: 'string|number' },
},
''
);
const sig = generateSignature('foo', {
params: [],
return: { type: 'string|number' },
});

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

it('preserves already spaced union return types', () => {
const sig = generateSignature(
'bar',
{
params: [],
return: { type: 'Promise<string> | undefined' },
},
''
);
const sig = generateSignature('bar', {
params: [],
return: { type: 'Promise<string> | undefined' },
});

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

it('omits return type when undefined', () => {
const sig = generateSignature(
'baz',
{
params: [],
return: undefined,
},
''
);
const sig = generateSignature('baz', {
params: [],
return: undefined,
});

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

it('handles empty return type', () => {
const sig = generateSignature('test', {
params: [],
return: null,
});

assert.strictEqual(sig, 'test(): void');
});

it('prefixes a constructor and infers its return type', () => {
const sig = generateSignature(
'test',
'Constructor',
{
params: [],
return: null,
return: undefined,
},
''
{ type: 'ctor' }
);

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

it('includes prefix when provided', () => {
it('keeps an explicit return type on a constructor', () => {
const sig = generateSignature(
'Constructor',
{
params: [],
return: undefined,
return: { type: 'net.Socket' },
},
'new '
{ type: 'ctor' }
);

assert.strictEqual(sig, 'new Constructor(): void');
assert.strictEqual(sig, 'new Constructor(): net.Socket');
});

it('handles complex union types with multiple pipes', () => {
it('falls back when a return entry carries no type', () => {
const sig = generateSignature(
'complexFunc',
'noType',
{
params: [],
return: { type: 'string|number|boolean|null' },
return: { type: undefined },
},
''
{ type: 'method' }
);

assert.strictEqual(sig, 'noType(): void');
});

it('handles complex union types with multiple pipes', () => {
const sig = generateSignature('complexFunc', {
params: [],
return: { type: 'string|number|boolean|null' },
});

assert.strictEqual(
sig,
'complexFunc(): string | number | boolean | null'
);
});

it('filters empty parts in union types', () => {
const sig = generateSignature(
'filterFunc',
{
params: [],
return: { type: 'string||number|' },
},
''
);
const sig = generateSignature('filterFunc', {
params: [],
return: { type: 'string||number|' },
});

assert.strictEqual(sig, 'filterFunc(): string | number');
});
});

describe('parameters', () => {
it('handles single parameter without optional flag or default', () => {
const sig = generateSignature(
'singleParam',
{
params: [{ name: 'value', optional: false }],
return: undefined,
},
''
);
const sig = generateSignature('singleParam', {
params: [{ name: 'value', optional: false }],
return: undefined,
});

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

it('handles multiple parameters', () => {
const sig = generateSignature(
'multiParam',
{
params: [
{ name: 'first', optional: false },
{ name: 'second', optional: false },
],
return: undefined,
},
''
);
const sig = generateSignature('multiParam', {
params: [
{ name: 'first', optional: false },
{ name: 'second', optional: false },
],
return: undefined,
});

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

it('marks optional parameters with question mark', () => {
const sig = generateSignature(
'optionalParam',
{
params: [
{ name: 'required', optional: false },
{ name: 'optional', optional: true },
],
return: undefined,
},
''
);
const sig = generateSignature('optionalParam', {
params: [
{ name: 'required', optional: false },
{ name: 'optional', optional: true },
],
return: undefined,
});

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

it('marks parameters with defaults as optional', () => {
const sig = generateSignature(
'defaultParam',
{
params: [
{ name: 'normal', optional: false },
{ name: 'withDefault', optional: false, default: 'defaultValue' },
],
return: undefined,
},
''
);
const sig = generateSignature('defaultParam', {
params: [
{ name: 'normal', optional: false },
{ name: 'withDefault', optional: false, default: 'defaultValue' },
],
return: undefined,
});

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

it('handles parameters that are both optional and have defaults', () => {
const sig = generateSignature(
'bothOptionalAndDefault',
{
params: [{ name: 'param', optional: true, default: 'value' }],
return: undefined,
},
''
);
const sig = generateSignature('bothOptionalAndDefault', {
params: [{ name: 'param', optional: true, default: 'value' }],
return: undefined,
});

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

it('handles empty params array', () => {
const sig = generateSignature(
'noParams',
{
params: [],
return: { type: 'string' },
},
''
);
const sig = generateSignature('noParams', {
params: [],
return: { type: 'string' },
});

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

it('handles params without optional property', () => {
const sig = generateSignature(
'implicitOptional',
{
params: [{ name: 'param1' }, { name: 'param2', default: 'value' }],
return: undefined,
},
''
);
const sig = generateSignature('implicitOptional', {
params: [{ name: 'param1' }, { name: 'param2', default: 'value' }],
return: undefined,
});

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

describe('class signatures', () => {
it('generates class signature with extends clause', () => {
const sig = generateSignature(
'MyClass',
{
params: [],
extends: { type: 'BaseClass' },
},
''
);
const sig = generateSignature('MyClass', {
params: [],
extends: { type: 'BaseClass' },
});

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

it('generates class signature with extends and prefix', () => {
it('does not prefix a class signature', () => {
const sig = generateSignature(
'MyClass',
{
params: [],
extends: { type: 'BaseClass' },
},
'abstract '
{ type: 'class' }
);

assert.strictEqual(sig, 'class abstract MyClass extends BaseClass');
assert.strictEqual(sig, 'class MyClass extends BaseClass');
});

it('ignores params and return type for class with extends', () => {
const sig = generateSignature(
'MyClass',
{
params: [{ name: 'ignored', optional: false }],
return: { type: 'ignored' },
extends: { type: 'BaseClass' },
},
''
);
const sig = generateSignature('MyClass', {
params: [{ name: 'ignored', optional: false }],
return: { type: 'ignored' },
extends: { type: 'BaseClass' },
});

assert.strictEqual(sig, 'class MyClass extends BaseClass');
});
Expand All @@ -250,7 +218,7 @@ describe('generateSignature', () => {
params: [{ name: 'param', optional: false }],
return: { type: 'MyClass' },
},
'new '
{ type: 'ctor' }
);

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

describe('edge cases', () => {
it('handles null return type', () => {
const sig = generateSignature(
'nullReturn',
{
params: [],
return: null,
},
''
);
const sig = generateSignature('nullReturn', {
params: [],
return: null,
});

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

it('handles missing extends property', () => {
const sig = generateSignature(
'NoExtends',
{
params: [{ name: 'param' }],
return: { type: 'void' },
},
''
);
const sig = generateSignature('NoExtends', {
params: [{ name: 'param' }],
return: { type: 'void' },
});

assert.strictEqual(sig, 'NoExtends(param): void');
});
Expand Down
Loading
Loading