Skip to content
Draft
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
4 changes: 4 additions & 0 deletions src/generator/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const SCHEMA_KEYS = [
'title',
'description',
'$schema',
'$constraints',
'type',
'tsType',
'markdownType',
Expand Down Expand Up @@ -187,6 +188,9 @@ function generateJSDoc (schema: Schema, opts: GenerateTypesOptions): string[] {
if (!SCHEMA_KEYS.includes(key)) {
buff.push('', `@${key} ${schema[key]}`)
}
if (key === '$constraints') {
buff.push(...Object.entries(schema[key]).map(([pkg, version]) => `@requires ${pkg}@${version}`))
}
}

if (Array.isArray(schema.tags)) {
Expand Down
8 changes: 8 additions & 0 deletions src/loader/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ function parseJSDocs (input: string | string[]): Schema {
return typedefs
}, {} as Record<string, string>)
for (const tag of tags) {
if (tag.startsWith('@requires')) {
const { pkg, version } = tag.match(/^@requires\s+(?<pkg>([^\s/][^@\s/]+\/)*[^\s/][^@\s]+)(@(?<version>.+))?$/)?.groups || {}
if (pkg) {
schema.$constraints = schema.$constraints || {}
schema.$constraints[pkg] = version || '*'
}
continue
}
if (tag.startsWith('@type')) {
const type = tag.match(/@type\s+\{([\S\s]+)\}/)?.[1]
// Skip typedefs
Expand Down
17 changes: 13 additions & 4 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import type { InputObject, InputValue, JSValue, Schema } from './types'

interface _ResolveCtx {
root: InputObject
defaults?: InputObject,
defaults?: InputObject
limiter?: (constraints: Record<string, string>) => boolean
resolveCache: Record<string, Schema>
}

export function resolveSchema (obj: InputObject, defaults?: InputObject) {
export function resolveSchema (obj: InputObject, defaults?: InputObject, resolveOptions?: Pick<_ResolveCtx, 'limiter'>) {
const schema = _resolveSchema(obj, '', {
...resolveOptions,
root: obj,
defaults,
resolveCache: {}
Expand Down Expand Up @@ -42,6 +44,10 @@ function _resolveSchema (input: InputValue, id: string, ctx: _ResolveCtx): Schem
// Clone to avoid mutation
const node = { ...input as any } as InputObject

if (ctx.limiter && '$constraints' in node && !ctx.limiter(node.$constraints)) {
return undefined
}

const schema: Schema = ctx.resolveCache[id] = {
...node.$schema,
id: '#' + id.replace(/\./g, '/')
Expand All @@ -50,12 +56,15 @@ function _resolveSchema (input: InputValue, id: string, ctx: _ResolveCtx): Schem
// Resolve children
for (const key in node) {
// Ignore special keys
if (key === '$resolve' || key === '$schema' || key === '$default') {
if (key === '$resolve' || key === '$schema' || key === '$default' || key === '$constraints') {
continue
}
schema.properties = schema.properties || {}
if (!schema.properties[key]) {
schema.properties[key] = _resolveSchema(node[key], joinPath(id, key), ctx)
const value = _resolveSchema(node[key], joinPath(id, key), ctx)
if (value !== undefined) {
schema.properties[key] = _resolveSchema(node[key], joinPath(id, key), ctx)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface Schema extends TypeDescriptor {
title?: string
description?: string
$schema?: string
$constraints?: Record<string, string>
tags?: string[]
args?: FunctionArg[]
returns?: TypeDescriptor,
Expand Down
40 changes: 40 additions & 0 deletions test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,46 @@ describe('resolveSchema', () => {
})
})

it('with constraints', () => {
const schema = resolveSchema({
foo: {
$default: '123',
$resolve: val => parseInt(val),
$constraints: { test: 'bar' }
},
bar: {
$default: '123',
$resolve: val => parseInt(val),
$constraints: { test: 'baz' }
},
qux: {
$default: '123',
$resolve: val => parseInt(val)
}
}, undefined, { limiter: constraints => constraints.test === 'bar' })

expect(schema).toMatchObject({
default: {
foo: 123,
qux: 123
},
id: '#',
properties: {
foo: {
default: 123,
id: '#foo',
type: 'number'
},
qux: {
default: 123,
id: '#qux',
type: 'number'
}
},
type: 'object'
})
})

it('array', () => {
const schema = resolveSchema({
empty: [],
Expand Down
8 changes: 7 additions & 1 deletion test/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ describe('transform (jsdoc)', () => {
* \`\`\`
*
* @see https://nuxtjs.org
* @requires [email protected]
* @requires pkg2
*/
srcDir: 'src'
}
Expand All @@ -173,7 +175,11 @@ describe('transform (jsdoc)', () => {
'@note This is a note.\nthat is on two lines',
'@example\n```js\nexport default secretNumber = 42\n```',
'@see https://nuxtjs.org'
]
],
$constraints: {
nuxt: '1.2.3',
pkg2: '*'
}
}
}
})
Expand Down
6 changes: 5 additions & 1 deletion test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ describe('resolveSchema', () => {
$default: 'test value',
$schema: {
title: 'Test',
description: 'this is test'
description: 'this is test',
$constraints: {
nuxt: '^1'
}
}
}
}
Expand All @@ -21,6 +24,7 @@ export interface Untyped {
* Test
* this is test
* @default "test value"
* @requires nuxt@^1
*/
foo: string,
},
Expand Down