@kubb/react
Version:
React integration for Kubb, providing JSX runtime support and React component generation capabilities for code generation plugins.
237 lines (201 loc) • 6.14 kB
text/typescript
import { orderBy } from 'natural-orderby'
export type Param = {
/**
* `object` will return the pathParams as an object.
*
* `inline` will return the pathParams as comma separated params.
* @default `'inline'`
* @private
*/
mode?: 'object' | 'inline' | 'inlineSpread'
type?: 'string' | 'number' | (string & {})
optional?: boolean
/**
* @example test = "default"
*/
default?: string
/**
* Used for no TypeScript(with mode object)
* @example test: "default"
*/
value?: string
children?: Params
}
type ParamItem =
| (Pick<Param, 'mode' | 'type' | 'value'> & {
optional?: true
default?: never
children?: Params
})
| (Pick<Param, 'mode' | 'type' | 'value'> & {
optional?: false
default?: string
children?: Params
})
export type Params = Record<string, Param | undefined>
type Options = {
type: 'constructor' | 'call' | 'object' | 'objectValue'
transformName?: (name: string) => string
transformType?: (type: string) => string
}
function order(items: Array<[key: string, item?: ParamItem]>) {
return orderBy(
items.filter(Boolean),
[
([_key, item]) => {
if (item?.children) {
return undefined
}
return !item?.default
},
([_key, item]) => {
if (item?.children) {
return undefined
}
return !item?.optional
},
],
['desc', 'desc'],
)
}
function parseChild(key: string, item: ParamItem, options: Options): string | null {
// @ts-expect-error
const entries = order(Object.entries(item.children))
const types: string[] = []
const names: string[] = []
const optional = entries.every(([_key, item]) => item?.optional)
entries.forEach(([key, entryItem]) => {
if (entryItem) {
const name = parseItem(key, { ...entryItem, type: undefined }, options)
if (entryItem.children) {
const subTypes = Object.entries(entryItem.children)
.map(([key]) => {
return key
})
.join(', ')
if (subTypes) {
names.push(`${name}: { ${subTypes} }`)
} else {
names.push(name)
}
} else {
if (options.type === 'call' && options.transformName) {
names.push(`${key}: ${name}`)
} else {
names.push(name)
}
}
if (entries.some(([_key, item]) => item?.type)) {
types.push(parseItem(key, { ...entryItem, default: undefined }, options))
}
}
})
const name = item.mode === 'inline' ? key : names.length ? `{ ${names.join(', ')} }` : undefined
const type = item.type ? item.type : types.length ? `{ ${types.join('; ')} }` : undefined
if (!name) {
return null
}
return parseItem(
name,
{
type,
default: item.default,
optional: !item.default ? optional : undefined,
} as ParamItem,
options,
)
}
function parseItem(name: string, item: ParamItem, options: Options): string {
const acc: string[] = []
const transformedName = options.transformName ? options.transformName(name) : name
const transformedType = options.transformType && item.type ? options.transformType(item.type) : item.type
if (options.type === 'object') {
return transformedName
}
if (options.type === 'objectValue') {
return item.value ? `${transformedName}: ${item.value}` : transformedName
}
//LEGACY
if (item.type && options.type === 'constructor') {
if (item.optional) {
acc.push(`${transformedName}?: ${transformedType}`)
} else {
acc.push(`${transformedName}: ${transformedType}${item.default ? ` = ${item.default}` : ''}`)
}
} else if (item.default && options.type === 'constructor') {
acc.push(`${transformedName} = ${item.default}`)
} else if (item.value) {
acc.push(`${transformedName} : ${item.value}`)
} else if (item.mode === 'inlineSpread') {
acc.push(`... ${transformedName}`)
} else {
acc.push(transformedName)
}
return acc[0] as string
}
export function getFunctionParams(params: Params, options: Options): string {
const entries = order(Object.entries(params as Record<string, ParamItem | undefined>))
return entries
.reduce((acc, [key, item]) => {
if (!item) {
return acc
}
if (item.children) {
if (Object.keys(item.children).length === 0) {
return acc
}
if (item.mode === 'inlineSpread') {
return [...acc, getFunctionParams(item.children, options)]
}
const parsedItem = parseChild(key, item, options)
if (!parsedItem) {
return acc
}
return [...acc, parsedItem]
}
const parsedItem = parseItem(key, item, options)
return [...acc, parsedItem]
}, [] as string[])
.join(', ')
}
export function createFunctionParams(params: Params): Params {
return params
}
// TODO use of zod
//TODO use of string as `$name: $type` to create templates for functions instead of call/constructor
export class FunctionParams {
#params: Params
static factory(params: Params) {
return new FunctionParams(params)
}
constructor(params: Params) {
this.#params = params
}
get params(): Params {
return this.#params
}
get flatParams(): Params {
const flatter = (acc: Params, [key, item]: [key: string, item?: Param]): Params => {
if (item?.children) {
return Object.entries(item.children).reduce(flatter, acc)
}
if (item) {
acc[key] = item
}
return acc
}
return Object.entries(this.#params).reduce(flatter, {} as Params)
}
toCall({ transformName, transformType }: Pick<Options, 'transformName' | 'transformType'> = {}): string {
return getFunctionParams(this.#params, { type: 'call', transformName, transformType })
}
toObject(): string {
return getFunctionParams(this.#params, { type: 'object' })
}
toObjectValue(): string {
return getFunctionParams(this.#params, { type: 'objectValue' })
}
toConstructor(): string {
return getFunctionParams(this.#params, { type: 'constructor' })
}
}