Skip to content

Commit fec194f

Browse files
authored
Merge pull request #515 from traversable/valibot-to-type-more-robust-jsdocs
feat(valibot): adds more robust support for JSDocs in `vx.toType` (#514)
2 parents b14b71a + 248f552 commit fec194f

3 files changed

Lines changed: 92 additions & 13 deletions

File tree

.changeset/large-rabbits-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@traversable/valibot": patch
3+
---
4+
5+
feat(valibot): adds `vx.toType` JSDoc support for `v.title`, `v.metadata` and defaults

packages/valibot/src/to-type.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,17 +190,30 @@ const fold = F.fold<string>((x, ix, input) => {
190190
const REST = 'rest' in option && typeof option.rest === 'string' ? `{ [x: string]: ${option.rest} }` : ''
191191
const OPT = Object.entries(original.entries).filter(([, v]) => hasOptional(v)).map(([k]) => k)
192192
const xs = Object.entries(option.entries).map(
193-
([k, v]) => {
194-
const description = getDescription(original[k])
193+
([k, value]) => {
194+
const originalValue = original.entries[k]
195+
const description = v.getDescription(originalValue)
196+
const title = v.getTitle(originalValue)
197+
const default_ = v.getDefault(originalValue)
198+
const metadata = v.getMetadata(originalValue)
199+
const $description = description === undefined ? null : ` * @description ${escapeJsDoc(description)}`
200+
const $title = title === undefined ? null : ` * @title ${escapeJsDoc(title)}`
201+
const $default = default_ === undefined ? null : ` * @default ${escapeJsDoc(JSON.stringify(default_, null, 2).split('\n').map((line) => ` * ${line}`).join('\n'))}`
202+
const $metadata = Object.keys(metadata).length === 0 ? null : ` * @metadata \n${JSON.stringify(metadata, null, 2).split('\n').map((line) => ` * ${line}`).join('\n')}`
203+
const hasJsDoc = preserveJsDocsEnabled(ix) && ($default !== null || $description !== null || $title !== null || $metadata !== null)
204+
195205
const READONLY = isReadonly(original.entries[k]) ? 'readonly ' : ''
196-
const JSDOCS = description == null || !preserveJsDocsEnabled(ix) ? null : [
206+
const JSDOCS = !hasJsDoc ? null : [
197207
'\n/**',
198-
` * ${escapeJsDoc(description)}`,
208+
$description,
209+
$default,
210+
$title,
211+
$metadata,
199212
' */',
200213
].filter((_) => _ !== null)
201214
return [
202215
JSDOCS === null ? null : JSDOCS.join('\n'),
203-
READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + v,
216+
READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + value,
204217
].filter((_) => _ !== null).join('\n')
205218
}
206219
)
@@ -244,17 +257,30 @@ const fold = F.fold<string>((x, ix, input) => {
244257
const READONLY_OPEN = isReadonly(input) ? 'Readonly<' : ''
245258
const READONLY_CLOSE = isReadonly(input) ? '>' : ''
246259
const xs = Object.entries(x.entries).map(
247-
([k, v]) => {
248-
const description = getDescription(input.entries[k])
260+
([k, value]) => {
261+
const originalValue = input.entries[k]
262+
const description = v.getDescription(originalValue)
263+
const title = v.getTitle(originalValue)
264+
const default_ = v.getDefault(originalValue)
265+
const metadata = v.getMetadata(originalValue)
266+
const $description = description === undefined ? null : ` * @description ${escapeJsDoc(description)}`
267+
const $title = title === undefined ? null : ` * @title ${escapeJsDoc(title)}`
268+
const $default = default_ === undefined ? null : ` * @default ${escapeJsDoc(JSON.stringify(default_, null, 2).split('\n').map((line) => ` * ${line}`).join('\n'))}`
269+
const $metadata = Object.keys(metadata).length === 0 ? null : ` * @metadata \n${JSON.stringify(metadata, null, 2).split('\n').map((line) => ` * ${line}`).join('\n')}`
270+
const hasJsDoc = preserveJsDocsEnabled(ix) && ($default !== null || $description !== null || $title !== null || $metadata !== null)
271+
249272
const READONLY = isReadonly(input.entries[k]) ? 'readonly ' : ''
250-
const JSDOCS = description == null || !preserveJsDocsEnabled(ix) ? null : [
273+
const JSDOCS = !hasJsDoc ? null : [
251274
'\n/**',
252-
` * ${escapeJsDoc(description)}`,
275+
$description,
276+
$default,
277+
$title,
278+
$metadata,
253279
' */',
254280
].filter((_) => _ !== null)
255281
return [
256282
JSDOCS === null ? null : JSDOCS.join('\n'),
257-
READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + v,
283+
READONLY + parseKey(k) + (OPT.includes(k) ? '?: ' : ': ') + value,
258284
].filter((_) => _ !== null).join('\n')
259285
}
260286
)

packages/valibot/test/to-type.test.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { vx } from '@traversable/valibot'
77
const format = (src: string) => prettier.format(src, { parser: 'typescript', semi: false })
88

99
vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/valibot❳: vx.toType', () => {
10-
vi.test('〖️⛳️〗› ❲vx.toType❳: jsdocs', () => {
10+
vi.test('〖️⛳️〗› ❲vx.toType❳: jsdoc descriptions', () => {
1111
vi.expect.soft(format(
1212
vx.toType(
1313
v.object({}),
@@ -31,17 +31,65 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/valibot❳: vx.toType', ()
3131
(`
3232
"type Type = {
3333
/**
34-
* abc description
34+
* @description abc description
3535
*/
3636
abc: number
3737
/**
38-
* def description
38+
* @description def description
3939
*/
4040
def: number
4141
}
4242
"
4343
`)
4444
})
45+
46+
vi.test('〖️⛳️〗› ❲vx.toType❳: jsdoc metadata', () => {
47+
vi.expect.soft(format(
48+
vx.toType(
49+
v.object({
50+
abc: v.pipe(
51+
v.string(),
52+
v.metadata({ a: 1, b: [2, 3, 4, 5] })
53+
)
54+
}),
55+
{ preserveJsDocs: true }
56+
)
57+
)).toMatchInlineSnapshot
58+
(`
59+
"{
60+
/**
61+
* @metadata
62+
* {
63+
* "a": 1,
64+
* "b": [
65+
* 2,
66+
* 3,
67+
* 4,
68+
* 5
69+
* ]
70+
* }
71+
*/
72+
abc: string
73+
}
74+
"
75+
`)
76+
})
77+
78+
vi.test('〖️⛳️〗› ❲vx.toType❳: setting `preserveJsDocs` to false ignores jsdocs', () => {
79+
vi.expect.soft(format(
80+
vx.toType(
81+
v.object({
82+
abc: v.pipe(v.number(), v.description('abc description')),
83+
def: v.pipe(v.number(), v.description('def description')),
84+
}),
85+
{ typeName: 'Type', preserveJsDocs: false }
86+
)
87+
)).toMatchInlineSnapshot
88+
(`
89+
"type Type = { abc: number; def: number }
90+
"
91+
`)
92+
})
4593
})
4694

4795
vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/valibot❳: vx.toType', () => {

0 commit comments

Comments
 (0)