diff --git a/.changeset/large-rabbits-hammer.md b/.changeset/large-rabbits-hammer.md new file mode 100644 index 00000000..2d72de47 --- /dev/null +++ b/.changeset/large-rabbits-hammer.md @@ -0,0 +1,5 @@ +--- +"@traversable/valibot": patch +--- + +feat(valibot): adds `vx.toType` JSDoc support for `v.title`, `v.metadata` and defaults diff --git a/packages/valibot/src/to-type.ts b/packages/valibot/src/to-type.ts index 126b457e..ae20487a 100644 --- a/packages/valibot/src/to-type.ts +++ b/packages/valibot/src/to-type.ts @@ -190,17 +190,30 @@ const fold = F.fold((x, ix, input) => { const REST = 'rest' in option && typeof option.rest === 'string' ? `{ [x: string]: ${option.rest} }` : '' const OPT = Object.entries(original.entries).filter(([, v]) => hasOptional(v)).map(([k]) => k) const xs = Object.entries(option.entries).map( - ([k, v]) => { - const description = getDescription(original[k]) + ([k, value]) => { + const originalValue = original.entries[k] + const description = v.getDescription(originalValue) + const title = v.getTitle(originalValue) + const default_ = v.getDefault(originalValue) + const metadata = v.getMetadata(originalValue) + const $description = description === undefined ? null : ` * @description ${escapeJsDoc(description)}` + const $title = title === undefined ? null : ` * @title ${escapeJsDoc(title)}` + const $default = default_ === undefined ? null : ` * @default ${escapeJsDoc(JSON.stringify(default_, null, 2).split('\n').map((line) => ` * ${line}`).join('\n'))}` + const $metadata = Object.keys(metadata).length === 0 ? null : ` * @metadata \n${JSON.stringify(metadata, null, 2).split('\n').map((line) => ` * ${line}`).join('\n')}` + const hasJsDoc = preserveJsDocsEnabled(ix) && ($default !== null || $description !== null || $title !== null || $metadata !== null) + const READONLY = isReadonly(original.entries[k]) ? 'readonly ' : '' - const JSDOCS = description == null || !preserveJsDocsEnabled(ix) ? null : [ + const JSDOCS = !hasJsDoc ? null : [ '\n/**', - ` * ${escapeJsDoc(description)}`, + $description, + $default, + $title, + $metadata, ' */', ].filter((_) => _ !== null) return [ JSDOCS === null ? null : JSDOCS.join('\n'), - READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + v, + READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + value, ].filter((_) => _ !== null).join('\n') } ) @@ -244,17 +257,30 @@ const fold = F.fold((x, ix, input) => { const READONLY_OPEN = isReadonly(input) ? 'Readonly<' : '' const READONLY_CLOSE = isReadonly(input) ? '>' : '' const xs = Object.entries(x.entries).map( - ([k, v]) => { - const description = getDescription(input.entries[k]) + ([k, value]) => { + const originalValue = input.entries[k] + const description = v.getDescription(originalValue) + const title = v.getTitle(originalValue) + const default_ = v.getDefault(originalValue) + const metadata = v.getMetadata(originalValue) + const $description = description === undefined ? null : ` * @description ${escapeJsDoc(description)}` + const $title = title === undefined ? null : ` * @title ${escapeJsDoc(title)}` + const $default = default_ === undefined ? null : ` * @default ${escapeJsDoc(JSON.stringify(default_, null, 2).split('\n').map((line) => ` * ${line}`).join('\n'))}` + const $metadata = Object.keys(metadata).length === 0 ? null : ` * @metadata \n${JSON.stringify(metadata, null, 2).split('\n').map((line) => ` * ${line}`).join('\n')}` + const hasJsDoc = preserveJsDocsEnabled(ix) && ($default !== null || $description !== null || $title !== null || $metadata !== null) + const READONLY = isReadonly(input.entries[k]) ? 'readonly ' : '' - const JSDOCS = description == null || !preserveJsDocsEnabled(ix) ? null : [ + const JSDOCS = !hasJsDoc ? null : [ '\n/**', - ` * ${escapeJsDoc(description)}`, + $description, + $default, + $title, + $metadata, ' */', ].filter((_) => _ !== null) return [ JSDOCS === null ? null : JSDOCS.join('\n'), - READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + v, + READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + value, ].filter((_) => _ !== null).join('\n') } ) diff --git a/packages/valibot/test/to-type.test.ts b/packages/valibot/test/to-type.test.ts index 4181f3c4..5df80a44 100644 --- a/packages/valibot/test/to-type.test.ts +++ b/packages/valibot/test/to-type.test.ts @@ -7,7 +7,7 @@ import { vx } from '@traversable/valibot' const format = (src: string) => prettier.format(src, { parser: 'typescript', semi: false }) vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/valibot❳: vx.toType', () => { - vi.test('〖️⛳️〗› ❲vx.toType❳: jsdocs', () => { + vi.test('〖️⛳️〗› ❲vx.toType❳: jsdoc descriptions', () => { vi.expect.soft(format( vx.toType( v.object({}), @@ -31,17 +31,65 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/valibot❳: vx.toType', () (` "type Type = { /** - * abc description + * @description abc description */ abc: number /** - * def description + * @description def description */ def: number } " `) }) + + vi.test('〖️⛳️〗› ❲vx.toType❳: jsdoc metadata', () => { + vi.expect.soft(format( + vx.toType( + v.object({ + abc: v.pipe( + v.string(), + v.metadata({ a: 1, b: [2, 3, 4, 5] }) + ) + }), + { preserveJsDocs: true } + ) + )).toMatchInlineSnapshot + (` + "{ + /** + * @metadata + * { + * "a": 1, + * "b": [ + * 2, + * 3, + * 4, + * 5 + * ] + * } + */ + abc: string + } + " + `) + }) + + vi.test('〖️⛳️〗› ❲vx.toType❳: setting `preserveJsDocs` to false ignores jsdocs', () => { + vi.expect.soft(format( + vx.toType( + v.object({ + abc: v.pipe(v.number(), v.description('abc description')), + def: v.pipe(v.number(), v.description('def description')), + }), + { typeName: 'Type', preserveJsDocs: false } + ) + )).toMatchInlineSnapshot + (` + "type Type = { abc: number; def: number } + " + `) + }) }) vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/valibot❳: vx.toType', () => {