@kubb/ast 4.36.1 → 5.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,195 @@
1
+ import type { FunctionNode, FunctionNodeType } from './nodes/function.ts'
2
+ import type { FunctionParameterNode, FunctionParametersNode, ObjectBindingParameterNode } from './nodes/index.ts'
3
+ import type { PrinterFactoryOptions } from './printer.ts'
4
+ import { createPrinterFactory } from './printer.ts'
5
+
6
+ /**
7
+ * Maps each `FunctionNodeType` key to its concrete node type.
8
+ */
9
+ export type FunctionNodeByType = {
10
+ functionParameter: FunctionParameterNode
11
+ objectBindingParameter: ObjectBindingParameterNode
12
+ functionParameters: FunctionParametersNode
13
+ }
14
+
15
+ const kindToHandlerKey = {
16
+ FunctionParameter: 'functionParameter',
17
+ ObjectBindingParameter: 'objectBindingParameter',
18
+ FunctionParameters: 'functionParameters',
19
+ } satisfies Record<string, FunctionNodeType>
20
+
21
+ /**
22
+ * Creates a named function-signature printer factory.
23
+ * Built on `createPrinterFactory` — dispatches on `node.kind` instead of `node.type`.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * type MyPrinter = PrinterFactoryOptions<'my', { mode: 'declaration' | 'call' }, string>
28
+ *
29
+ * export const myPrinter = defineFunctionPrinter<MyPrinter>((options) => ({
30
+ * name: 'my',
31
+ * options,
32
+ * nodes: {
33
+ * functionParameter(node) {
34
+ * return options.mode === 'declaration' && node.type ? `${node.name}: ${node.type}` : node.name
35
+ * },
36
+ * objectBindingParameter(node) {
37
+ * const inner = node.properties.map(p => this.print(p)).filter(Boolean).join(', ')
38
+ * return `{ ${inner} }`
39
+ * },
40
+ * functionParameters(node) {
41
+ * return node.params.map(p => this.print(p)).filter(Boolean).join(', ')
42
+ * },
43
+ * },
44
+ * }))
45
+ * ```
46
+ */
47
+ export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>((node) => kindToHandlerKey[node.kind])
48
+
49
+ /**
50
+ * The four rendering modes for a `FunctionParametersNode`.
51
+ *
52
+ * | Mode | Output example | Use case |
53
+ * |---------------|---------------------------------------------|---------------------------------|
54
+ * | `declaration` | `id: string, config: Config = {}` | Function parameter declarations |
55
+ * | `call` | `id, { method, url }` | Function call arguments |
56
+ * | `keys` | `{ id, config }` | Key names only (destructuring) |
57
+ * | `values` | `{ id: id, config: config }` | Key → value pairs |
58
+ */
59
+
60
+ export type FunctionPrinterOptions = {
61
+ mode: 'declaration' | 'call' | 'keys' | 'values'
62
+ /**
63
+ * Optional transformation applied to every parameter name before printing.
64
+ */
65
+ transformName?: (name: string) => string
66
+ /**
67
+ * Optional transformation applied to every type string before printing.
68
+ */
69
+ transformType?: (type: string) => string
70
+ }
71
+
72
+ type DefaultPrinter = PrinterFactoryOptions<'functionParameters', FunctionPrinterOptions, string>
73
+
74
+ function rank(param: FunctionParameterNode | ObjectBindingParameterNode): number {
75
+ if (param.kind === 'ObjectBindingParameter') {
76
+ if (param.default) return 2
77
+ const isOptional = param.optional ?? param.properties.every((p) => p.optional || p.default !== undefined)
78
+ return isOptional ? 1 : 0
79
+ }
80
+ if (param.rest) return 3
81
+ if (param.default) return 2
82
+ return param.optional ? 1 : 0
83
+ }
84
+
85
+ function sortParams(params: Array<FunctionParameterNode | ObjectBindingParameterNode>): Array<FunctionParameterNode | ObjectBindingParameterNode> {
86
+ return [...params].sort((a, b) => rank(a) - rank(b))
87
+ }
88
+
89
+ function sortChildParams(params: Array<FunctionParameterNode>): Array<FunctionParameterNode> {
90
+ return [...params].sort((a, b) => rank(a) - rank(b))
91
+ }
92
+
93
+ /**
94
+ * Default function-signature printer. Covers the four standard output modes
95
+ * used throughout Kubb plugins.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * const printer = functionSignaturePrinter({ mode: 'declaration' })
100
+ *
101
+ * const sig = createFunctionParameters({
102
+ * params: [
103
+ * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
104
+ * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
105
+ * ],
106
+ * })
107
+ *
108
+ * printer.print(sig) // → "petId: string, config: Config = {}"
109
+ * ```
110
+ */
111
+ export const functionPrinter = defineFunctionPrinter<DefaultPrinter>((options) => ({
112
+ name: 'functionParameters',
113
+ options,
114
+ nodes: {
115
+ functionParameter(node) {
116
+ const { mode, transformName, transformType } = this.options
117
+ const name = transformName ? transformName(node.name) : node.name
118
+ const type = node.type && transformType ? transformType(node.type) : node.type
119
+
120
+ if (mode === 'keys' || mode === 'values') {
121
+ return node.rest ? `...${name}` : name
122
+ }
123
+
124
+ if (mode === 'call') {
125
+ return node.rest ? `...${name}` : name
126
+ }
127
+
128
+ if (node.rest) {
129
+ return type ? `...${name}: ${type}` : `...${name}`
130
+ }
131
+ if (type) {
132
+ if (node.optional) return `${name}?: ${type}`
133
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`
134
+ }
135
+ return node.default ? `${name} = ${node.default}` : name
136
+ },
137
+ objectBindingParameter(node) {
138
+ const { mode, transformName, transformType } = this.options
139
+ const sorted = sortChildParams(node.properties)
140
+ const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== undefined)
141
+
142
+ if (node.inline) {
143
+ return sorted
144
+ .map((p) => this.print(p))
145
+ .filter(Boolean)
146
+ .join(', ')
147
+ }
148
+
149
+ if (mode === 'keys' || mode === 'values') {
150
+ const keys = sorted.map((p) => p.name).join(', ')
151
+ return `{ ${keys} }`
152
+ }
153
+
154
+ if (mode === 'call') {
155
+ const keys = sorted.map((p) => p.name).join(', ')
156
+ return `{ ${keys} }`
157
+ }
158
+
159
+ const names = sorted.map((p) => {
160
+ const n = transformName ? transformName(p.name) : p.name
161
+
162
+ return n
163
+ })
164
+
165
+ const nameStr = names.length ? `{ ${names.join(', ')} }` : undefined
166
+ if (!nameStr) return null
167
+
168
+ let typeAnnotation = node.type
169
+ if (!typeAnnotation) {
170
+ const typeParts = sorted
171
+ .filter((p) => p.type)
172
+ .map((p) => {
173
+ const t = transformType && p.type ? transformType(p.type) : p.type!
174
+ return p.optional || p.default !== undefined ? `${p.name}?: ${t}` : `${p.name}: ${t}`
175
+ })
176
+ typeAnnotation = typeParts.length ? `{ ${typeParts.join('; ')} }` : undefined
177
+ }
178
+
179
+ if (typeAnnotation) {
180
+ if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? '{}'}`
181
+ return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`
182
+ }
183
+
184
+ return node.default ? `${nameStr} = ${node.default}` : nameStr
185
+ },
186
+ functionParameters(node) {
187
+ const sorted = sortParams(node.params)
188
+
189
+ return sorted
190
+ .map((p) => this.print(p))
191
+ .filter(Boolean)
192
+ .join(', ')
193
+ },
194
+ },
195
+ }))
package/src/guards.ts CHANGED
@@ -1,4 +1,17 @@
1
- import type { Node, NodeKind, OperationNode, ParameterNode, PropertyNode, ResponseNode, RootNode, SchemaNode, SchemaNodeByType } from './nodes/index.ts'
1
+ import type {
2
+ FunctionParameterNode,
3
+ FunctionParametersNode,
4
+ Node,
5
+ NodeKind,
6
+ ObjectBindingParameterNode,
7
+ OperationNode,
8
+ ParameterNode,
9
+ PropertyNode,
10
+ ResponseNode,
11
+ RootNode,
12
+ SchemaNode,
13
+ SchemaNodeByType,
14
+ } from './nodes/index.ts'
2
15
 
3
16
  /**
4
17
  * Narrows a `SchemaNode` to the specific variant matching `type`.
@@ -40,3 +53,18 @@ export const isParameterNode = isKind<ParameterNode>('Parameter')
40
53
  * Type guard for `ResponseNode`.
41
54
  */
42
55
  export const isResponseNode = isKind<ResponseNode>('Response')
56
+
57
+ /**
58
+ * Type guard for `FunctionParameterNode`.
59
+ */
60
+ export const isFunctionParameterNode = isKind<FunctionParameterNode>('FunctionParameter')
61
+
62
+ /**
63
+ * Type guard for `ObjectBindingParameterNode`.
64
+ */
65
+ export const isObjectBindingParameterNode = isKind<ObjectBindingParameterNode>('ObjectBindingParameter')
66
+
67
+ /**
68
+ * Type guard for `FunctionParametersNode`.
69
+ */
70
+ export const isFunctionParametersNode = isKind<FunctionParametersNode>('FunctionParameters')
package/src/index.ts CHANGED
@@ -1,6 +1,29 @@
1
1
  export { httpMethods, mediaTypes, nodeKinds, schemaTypes } from './constants.ts'
2
- export { createOperation, createParameter, createProperty, createResponse, createRoot, createSchema } from './factory.ts'
3
- export { isOperationNode, isParameterNode, isPropertyNode, isResponseNode, isRootNode, isSchemaNode, narrowSchema } from './guards.ts'
2
+ export {
3
+ createFunctionParameter,
4
+ createFunctionParameters,
5
+ createObjectBindingParameter,
6
+ createOperation,
7
+ createParameter,
8
+ createProperty,
9
+ createResponse,
10
+ createRoot,
11
+ createSchema,
12
+ } from './factory.ts'
13
+ export { functionPrinter } from './functionPrinter.ts'
14
+ export {
15
+ isFunctionParameterNode,
16
+ isFunctionParametersNode,
17
+ isObjectBindingParameterNode,
18
+ isOperationNode,
19
+ isParameterNode,
20
+ isPropertyNode,
21
+ isResponseNode,
22
+ isRootNode,
23
+ isSchemaNode,
24
+ narrowSchema,
25
+ } from './guards.ts'
4
26
  export { definePrinter } from './printer.ts'
5
27
  export { buildRefMap, refMapToObject, resolveRef } from './refs.ts'
28
+ export { applyParamsCasing, isPlainStringType } from './utils.ts'
6
29
  export { collect, transform, walk } from './visitor.ts'
package/src/mocks.ts CHANGED
@@ -20,7 +20,13 @@ export function buildSampleTree(): RootNode {
20
20
  path: '/pets/{petId}',
21
21
  tags: ['pets'],
22
22
  parameters: [createParameter({ name: 'petId', in: 'path', schema: createSchema({ type: 'integer' }), required: true })],
23
- responses: [createResponse({ statusCode: '200', schema: createSchema({ type: 'ref', name: 'Pet' }) }), createResponse({ statusCode: '404' })],
23
+ responses: [
24
+ createResponse({ statusCode: '200', schema: createSchema({ type: 'ref', name: 'Pet' }) }),
25
+ createResponse({
26
+ statusCode: '404',
27
+ schema: createSchema({ type: 'ref', name: 'Error' }),
28
+ }),
29
+ ],
24
30
  })
25
31
 
26
32
  return createRoot({ schemas: [petSchema], operations: [operation] })
package/src/nodes/base.ts CHANGED
@@ -1,7 +1,16 @@
1
1
  /**
2
2
  * Kind discriminant for every AST node.
3
3
  */
4
- export type NodeKind = 'Root' | 'Operation' | 'Schema' | 'Property' | 'Parameter' | 'Response'
4
+ export type NodeKind =
5
+ | 'Root'
6
+ | 'Operation'
7
+ | 'Schema'
8
+ | 'Property'
9
+ | 'Parameter'
10
+ | 'Response'
11
+ | 'FunctionParameter'
12
+ | 'ObjectBindingParameter'
13
+ | 'FunctionParameters'
5
14
 
6
15
  /**
7
16
  * Common base for all AST nodes.
@@ -0,0 +1,119 @@
1
+ import type { BaseNode } from './base.ts'
2
+
3
+ /**
4
+ * A single named function parameter.
5
+ *
6
+ * @example Simple required param
7
+ * `name: Type`
8
+ *
9
+ * @example Optional param
10
+ * `name?: Type`
11
+ *
12
+ * @example Param with default
13
+ * `name: Type = defaultValue`
14
+ *
15
+ * @example Rest / spread param
16
+ * `...name: Type[]`
17
+ */
18
+ export type FunctionParameterNode = BaseNode & {
19
+ kind: 'FunctionParameter'
20
+ /**
21
+ * The parameter name as it appears in the function signature.
22
+ */
23
+ name: string
24
+ /**
25
+ * TypeScript type annotation (raw string, e.g. `"string"`, `"Pet[]"`, `"Partial<Config>"`).
26
+ * Omit for untyped JavaScript output.
27
+ */
28
+ type?: string
29
+ /**
30
+ * When `true` the parameter is emitted as a rest parameter (`...name`).
31
+ * @default false
32
+ */
33
+ rest?: boolean
34
+ } /**
35
+ * Explicitly optional parameter — rendered with `?` in the signature.
36
+ * Cannot be combined with `default`; a parameter with a default is implicitly optional.
37
+ * @example `name?: Type`
38
+ */ & (
39
+ | { optional: true; default?: never }
40
+ /**
41
+ * Required parameter, or a parameter with a default value (implicitly optional).
42
+ * @example Required: `name: Type`
43
+ * @example With default: `name: Type = default`
44
+ */
45
+ | { optional?: false; default?: string }
46
+ )
47
+
48
+ /**
49
+ * An object-destructured function parameter group.
50
+ *
51
+ * Renders as `{ key1, key2 }: { key1: Type1; key2: Type2 } = {}` in a declaration,
52
+ * or as individual top-level params when `inline` is `true`.
53
+ *
54
+ * Replaces `mode: 'object'` / `mode: 'inlineSpread'` from the legacy `FunctionParams` API.
55
+ *
56
+ * @example Object destructuring with auto-computed type (declaration)
57
+ * `{ id, name }: { id: string; name: string } = {}`
58
+ *
59
+ * @example Inline (spread) — children emitted as individual top-level params
60
+ * `id: string, name: string`
61
+ */
62
+ export type ObjectBindingParameterNode = BaseNode & {
63
+ kind: 'ObjectBindingParameter'
64
+ /**
65
+ * The individual parameters that form the destructured object.
66
+ * Rendered as `{ key1, key2 }` in declarations, or spread inline when `inline` is `true`.
67
+ */
68
+ properties: Array<FunctionParameterNode>
69
+ /**
70
+ * Explicit TypeScript type annotation for the whole object param.
71
+ * When absent the printer auto-computes `{ key1: Type1; key2: Type2 }` from `params`.
72
+ */
73
+ type?: string
74
+ /**
75
+ * When `true` the `params` are emitted as individual top-level parameters instead of
76
+ * being wrapped in a destructuring pattern (`{ key1, key2 }`).
77
+ *
78
+ * Equivalent to `mode: 'inlineSpread'` in the legacy `FunctionParams` API.
79
+ * @default false
80
+ */
81
+ inline?: boolean
82
+ /**
83
+ * Whether the whole object group is optional.
84
+ * When absent the printer auto-computes this: optional if every child param is optional.
85
+ */
86
+ optional?: boolean
87
+ /**
88
+ * Default value for the object group, written verbatim after `=`.
89
+ * Commonly `'{}'` to allow omitting the argument entirely.
90
+ */
91
+ default?: string
92
+ }
93
+
94
+ /**
95
+ * A complete ordered parameter list for a function.
96
+ *
97
+ * The printer is responsible for sorting (required → optional → has-default).
98
+ * Nodes themselves are treated as plain, immutable data.
99
+ *
100
+ * Renders differently depending on the output mode:
101
+ * - `declaration` → `(id: string, config: Config = {})` — typed function parameter list
102
+ * - `call` → `(id, { method, url })` — function call arguments
103
+ * - `keys` → `{ id, config }` — key names only (for destructuring)
104
+ * - `values` → `{ id: id, config: config }` — key → value pairs
105
+ */
106
+ export type FunctionParametersNode = BaseNode & {
107
+ kind: 'FunctionParameters'
108
+ params: Array<FunctionParameterNode | ObjectBindingParameterNode>
109
+ }
110
+
111
+ /**
112
+ * The three function-signature AST node variants.
113
+ */
114
+ export type FunctionNode = FunctionParameterNode | ObjectBindingParameterNode | FunctionParametersNode
115
+
116
+ /**
117
+ * Handler map keys — one per `FunctionNode` kind.
118
+ */
119
+ export type FunctionNodeType = 'functionParameter' | 'objectBindingParameter' | 'functionParameters'
@@ -1,3 +1,4 @@
1
+ import type { FunctionNode } from './function.ts'
1
2
  import type { OperationNode } from './operation.ts'
2
3
  import type { ParameterNode } from './parameter.ts'
3
4
  import type { PropertyNode } from './property.ts'
@@ -6,6 +7,7 @@ import type { RootNode } from './root.ts'
6
7
  import type { SchemaNode } from './schema.ts'
7
8
 
8
9
  export type { BaseNode, NodeKind } from './base.ts'
10
+ export type { FunctionNode, FunctionNodeType, FunctionParameterNode, FunctionParametersNode, ObjectBindingParameterNode } from './function.ts'
9
11
  export type { HttpStatusCode, MediaType, StatusCode } from './http.ts'
10
12
  export type { HttpMethod, OperationNode } from './operation.ts'
11
13
  export type { ParameterLocation, ParameterNode } from './parameter.ts'
@@ -33,6 +35,7 @@ export type {
33
35
  StringSchemaNode,
34
36
  TimeSchemaNode,
35
37
  UnionSchemaNode,
38
+ UrlSchemaNode,
36
39
  } from './schema.ts'
37
40
 
38
41
  /**
@@ -42,4 +45,4 @@ export type {
42
45
  * TypeScript narrow the type automatically inside `switch (node.kind)`
43
46
  * blocks, eliminating the need for manual `as TypeName` casts.
44
47
  */
45
- export type Node = RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode
48
+ export type Node = RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode | FunctionNode
@@ -15,6 +15,10 @@ export type OperationNode = BaseNode & {
15
15
  */
16
16
  operationId: string
17
17
  method: HttpMethod
18
+ /**
19
+ * Express-style path string, e.g. `/pets/:petId`.
20
+ * Derived from the OpenAPI path by converting `{param}` tokens to `:param`.
21
+ */
18
22
  path: string
19
23
  tags: Array<string>
20
24
  summary?: string
@@ -12,6 +12,6 @@ export type ResponseNode = BaseNode & {
12
12
  */
13
13
  statusCode: StatusCode
14
14
  description?: string
15
- schema?: SchemaNode
15
+ schema: SchemaNode
16
16
  mediaType?: MediaType
17
17
  }
package/src/nodes/root.ts CHANGED
@@ -7,9 +7,17 @@ import type { SchemaNode } from './schema.ts'
7
7
  * Adapters populate whichever fields are available in their source format.
8
8
  */
9
9
  export type RootMeta = {
10
- /** API title (from `info.title` in OAS/AsyncAPI). */
10
+ /**
11
+ * API title (from `info.title` in OAS/AsyncAPI).
12
+ */
11
13
  title?: string
12
- /** API version string (from `info.version` in OAS/AsyncAPI). */
14
+ /**
15
+ * API description (from `info.description` in OAS/AsyncAPI).
16
+ */
17
+ description?: string
18
+ /**
19
+ * API version string (from `info.version` in OAS/AsyncAPI).
20
+ */
13
21
  version?: string
14
22
  /**
15
23
  * Resolved base URL for the API.
@@ -27,6 +35,8 @@ export type RootNode = BaseNode & {
27
35
  kind: 'Root'
28
36
  schemas: Array<SchemaNode>
29
37
  operations: Array<OperationNode>
30
- /** Format-agnostic document metadata populated by the adapter. */
38
+ /**
39
+ * Format-agnostic document metadata populated by the adapter.
40
+ */
31
41
  meta?: RootMeta
32
42
  }
@@ -1,7 +1,20 @@
1
1
  import type { BaseNode } from './base.ts'
2
2
  import type { PropertyNode } from './property.ts'
3
3
 
4
- export type PrimitiveSchemaType = 'string' | 'number' | 'integer' | 'bigint' | 'boolean' | 'null' | 'any' | 'unknown' | 'void' | 'object' | 'array' | 'date'
4
+ export type PrimitiveSchemaType =
5
+ | 'string'
6
+ | 'number'
7
+ | 'integer'
8
+ | 'bigint'
9
+ | 'boolean'
10
+ | 'null'
11
+ | 'any'
12
+ | 'unknown'
13
+ | 'void'
14
+ | 'never'
15
+ | 'object'
16
+ | 'array'
17
+ | 'date'
5
18
 
6
19
  export type ComplexSchemaType = 'tuple' | 'union' | 'intersection' | 'enum'
7
20
 
@@ -14,7 +27,7 @@ export type SchemaType = PrimitiveSchemaType | ComplexSchemaType | SpecialSchema
14
27
 
15
28
  export type ScalarSchemaType = Exclude<
16
29
  SchemaType,
17
- 'object' | 'array' | 'tuple' | 'union' | 'intersection' | 'enum' | 'ref' | 'datetime' | 'date' | 'time' | 'string' | 'number' | 'integer' | 'bigint'
30
+ 'object' | 'array' | 'tuple' | 'union' | 'intersection' | 'enum' | 'ref' | 'datetime' | 'date' | 'time' | 'string' | 'number' | 'integer' | 'bigint' | 'url'
18
31
  >
19
32
 
20
33
  /**
@@ -206,6 +219,17 @@ export type ScalarSchemaNode = SchemaNodeBase & {
206
219
  type: ScalarSchemaType
207
220
  }
208
221
 
222
+ /**
223
+ * URL schema, optionally carrying an Express-style path template for template literal generation.
224
+ */
225
+ export type UrlSchemaNode = SchemaNodeBase & {
226
+ type: 'url'
227
+ /**
228
+ * Express-style path (e.g. `'/pets/:petId'`). When set, printers may emit a template literal type.
229
+ */
230
+ path?: string
231
+ }
232
+
209
233
  /**
210
234
  * Maps each schema type string to its `SchemaNode` variant. Used by `narrowSchema`.
211
235
  */
@@ -229,9 +253,10 @@ export type SchemaNodeByType = {
229
253
  any: ScalarSchemaNode
230
254
  unknown: ScalarSchemaNode
231
255
  void: ScalarSchemaNode
256
+ never: ScalarSchemaNode
232
257
  uuid: ScalarSchemaNode
233
258
  email: ScalarSchemaNode
234
- url: ScalarSchemaNode
259
+ url: UrlSchemaNode
235
260
  blob: ScalarSchemaNode
236
261
  }
237
262
 
@@ -250,4 +275,5 @@ export type SchemaNode =
250
275
  | TimeSchemaNode
251
276
  | StringSchemaNode
252
277
  | NumberSchemaNode
278
+ | UrlSchemaNode
253
279
  | ScalarSchemaNode