@kubb/ast 5.0.0-beta.56 → 5.0.0-beta.57

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.
package/src/guards.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { HttpOperationNode, InputNode, Node, NodeKind, OperationNode, OutputNode, SchemaNode, SchemaNodeByType } from './nodes/index.ts'
1
+ import type { HttpOperationNode, OperationNode, SchemaNode, SchemaNodeByType } from './nodes/index.ts'
2
2
 
3
3
  /**
4
4
  * Narrows a `SchemaNode` to the variant that matches `type`.
@@ -13,46 +13,6 @@ export function narrowSchema<T extends SchemaNode['type']>(node: SchemaNode | un
13
13
  return node?.type === type ? (node as SchemaNodeByType[T]) : null
14
14
  }
15
15
 
16
- function isKind<T extends Node>(kind: NodeKind) {
17
- return (node: unknown): node is T => (node as Node).kind === kind
18
- }
19
-
20
- /**
21
- * Returns `true` when the input is an `InputNode`.
22
- *
23
- * @example
24
- * ```ts
25
- * if (isInputNode(node)) {
26
- * console.log(node.schemas.length)
27
- * }
28
- * ```
29
- */
30
- export const isInputNode = isKind<InputNode>('Input')
31
-
32
- /**
33
- * Returns `true` when the input is an `OutputNode`.
34
- *
35
- * @example
36
- * ```ts
37
- * if (isOutputNode(node)) {
38
- * console.log(node.files.length)
39
- * }
40
- * ```
41
- */
42
- export const isOutputNode = isKind<OutputNode>('Output')
43
-
44
- /**
45
- * Returns `true` when the input is an `OperationNode`.
46
- *
47
- * @example
48
- * ```ts
49
- * if (isOperationNode(node)) {
50
- * console.log(node.operationId)
51
- * }
52
- * ```
53
- */
54
- export const isOperationNode = isKind<OperationNode>('Operation')
55
-
56
16
  /**
57
17
  * Narrows an `OperationNode` to an `HttpOperationNode`, guaranteeing `method` and `path`.
58
18
  *
@@ -66,15 +26,3 @@ export const isOperationNode = isKind<OperationNode>('Operation')
66
26
  export function isHttpOperationNode(node: OperationNode): node is HttpOperationNode {
67
27
  return node.protocol === 'http' || (node.method !== undefined && node.path !== undefined)
68
28
  }
69
-
70
- /**
71
- * Returns `true` when the input is a `SchemaNode`.
72
- *
73
- * @example
74
- * ```ts
75
- * if (isSchemaNode(node)) {
76
- * console.log(node.type)
77
- * }
78
- * ```
79
- */
80
- export const isSchemaNode = isKind<SchemaNode>('Schema')
package/src/index.ts CHANGED
@@ -1,34 +1,49 @@
1
1
  export { httpMethods, schemaTypes } from './constants.ts'
2
2
  export { applyDedupe, buildDedupePlan } from './dedupe.ts'
3
3
  export { defineSchemaDialect } from './dialect.ts'
4
+ export { createFile, update } from './factory.ts'
5
+ export { isHttpOperationNode, narrowSchema } from './guards.ts'
6
+ export { defineNode } from './node.ts'
7
+ export type { NodeDef } from './node.ts'
8
+ export { syncOptionality } from './node.ts'
4
9
  export {
10
+ arrowFunctionDef,
11
+ breakDef,
12
+ constDef,
5
13
  createArrowFunction,
6
14
  createBreak,
7
15
  createConst,
8
- createExport,
9
- createFile,
10
16
  createFunction,
11
- createFunctionParameter,
12
- createFunctionParameters,
13
- createImport,
14
- createInput,
15
- createStreamInput,
16
17
  createJsx,
17
- createOperation,
18
- createOutput,
19
- createParameter,
20
- createParameterGroup,
21
- createParamsType,
22
- createProperty,
23
- createResponse,
24
- createSchema,
25
- createSource,
26
18
  createText,
27
19
  createType,
28
- syncOptionality,
29
- update,
30
- } from './factory.ts'
31
- export { isHttpOperationNode, isInputNode, isOperationNode, isOutputNode, isSchemaNode, narrowSchema } from './guards.ts'
20
+ functionDef,
21
+ jsxDef,
22
+ textDef,
23
+ typeDef,
24
+ } from './nodes/code.ts'
25
+ export { contentDef } from './nodes/content.ts'
26
+ export { createExport, createImport, createSource, exportDef, fileDef, importDef, sourceDef } from './nodes/file.ts'
27
+ export {
28
+ createFunctionParameter,
29
+ createFunctionParameters,
30
+ createIndexedAccessType,
31
+ createObjectBindingPattern,
32
+ createTypeLiteral,
33
+ functionParameterDef,
34
+ functionParametersDef,
35
+ indexedAccessTypeDef,
36
+ objectBindingPatternDef,
37
+ typeLiteralDef,
38
+ } from './nodes/function.ts'
39
+ export { createInput, createStreamInput, inputDef } from './nodes/input.ts'
40
+ export { createOperation, operationDef } from './nodes/operation.ts'
41
+ export { createOutput, outputDef } from './nodes/output.ts'
42
+ export { createParameter, parameterDef } from './nodes/parameter.ts'
43
+ export { createProperty, propertyDef } from './nodes/property.ts'
44
+ export { requestBodyDef } from './nodes/requestBody.ts'
45
+ export { createResponse, responseDef } from './nodes/response.ts'
46
+ export { createSchema, schemaDef } from './nodes/schema.ts'
32
47
  export { createPrinterFactory, definePrinter } from './printer.ts'
33
48
  export { signatureOf } from './signature.ts'
34
49
  export { mergeAdjacentObjectsLazy, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
package/src/mocks.ts CHANGED
@@ -1,5 +1,10 @@
1
- import { createInput, createOperation, createParameter, createProperty, createResponse, createSchema } from './factory.ts'
1
+ import { createInput } from './nodes/input.ts'
2
2
  import type { InputNode } from './nodes/input.ts'
3
+ import { createOperation } from './nodes/operation.ts'
4
+ import { createParameter } from './nodes/parameter.ts'
5
+ import { createProperty } from './nodes/property.ts'
6
+ import { createResponse } from './nodes/response.ts'
7
+ import { createSchema } from './nodes/schema.ts'
3
8
 
4
9
  /**
5
10
  * Builds a minimal sample AST with one `Pet` schema and one `getPetById` operation.
package/src/node.ts ADDED
@@ -0,0 +1,128 @@
1
+ import type { BaseNode, NodeKind } from './nodes/base.ts'
2
+ import type { SchemaNode } from './nodes/index.ts'
3
+
4
+ /**
5
+ * Visitor callback names, one per traversable node kind. Kept in sync with the
6
+ * keys of `Visitor` in `visitor.ts`.
7
+ */
8
+ type VisitorKey = 'input' | 'output' | 'operation' | 'schema' | 'property' | 'parameter' | 'response'
9
+
10
+ /**
11
+ * Distributive `Omit` that preserves each member of a union.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * type A = { kind: 'a'; keep: string; drop: number }
16
+ * type B = { kind: 'b'; keep: boolean; drop: number }
17
+ * type Result = DistributiveOmit<A | B, 'drop'>
18
+ * // -> { kind: 'a'; keep: string } | { kind: 'b'; keep: boolean }
19
+ * ```
20
+ */
21
+ export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never
22
+
23
+ /**
24
+ * Builds a type guard that matches nodes of the given `kind`.
25
+ */
26
+ export function isKind<T extends BaseNode>(kind: NodeKind) {
27
+ return (node: unknown): node is T => (node as BaseNode).kind === kind
28
+ }
29
+
30
+ /**
31
+ * Updates a schema's `optional` and `nullish` flags from a parent's `required`
32
+ * value and the schema's own `nullable`. Mirrors how OpenAPI parameters and
33
+ * object properties combine "required" and "nullable" into a single AST.
34
+ *
35
+ * - Non-required + non-nullable → `optional: true`.
36
+ * - Non-required + nullable → `nullish: true`.
37
+ * - Required → both flags cleared.
38
+ */
39
+ export function syncOptionality(schema: SchemaNode, required: boolean): SchemaNode {
40
+ const nullable = schema.nullable ?? false
41
+
42
+ return {
43
+ ...schema,
44
+ optional: !required && !nullable ? true : undefined,
45
+ nullish: !required && nullable ? true : undefined,
46
+ }
47
+ }
48
+
49
+ /**
50
+ * The single definition derived from one {@link defineNode} call: the node's
51
+ * `create` builder, its `is` guard, and the traversal metadata the registry
52
+ * collects into the visitor tables.
53
+ */
54
+ export type NodeDef<TNode extends BaseNode = BaseNode, TInput = never> = {
55
+ /**
56
+ * Node discriminator this definition owns.
57
+ */
58
+ kind: NodeKind
59
+ /**
60
+ * Builds a node from its input, applying `defaults` and the optional `build` hook.
61
+ */
62
+ create: (input: TInput) => TNode
63
+ /**
64
+ * Type guard matching this node kind.
65
+ */
66
+ is: (node: unknown) => node is TNode
67
+ /**
68
+ * Child node fields in traversal order. Feeds `VISITOR_KEYS`.
69
+ */
70
+ children?: ReadonlyArray<string>
71
+ /**
72
+ * Visitor callback name. Feeds `VISITOR_KEY_BY_KIND`.
73
+ */
74
+ visitorKey?: VisitorKey
75
+ /**
76
+ * When `true`, `create` is rerun after children are rebuilt so computed fields
77
+ * stay in sync. Feeds `nodeRebuilders`.
78
+ */
79
+ rebuild?: boolean
80
+ }
81
+
82
+ type DefineNodeConfig<TNode extends BaseNode, TInput, TBuilt extends object> = {
83
+ kind: TNode['kind']
84
+ defaults?: Partial<TNode>
85
+ build?: (input: TInput) => TBuilt
86
+ children?: ReadonlyArray<string>
87
+ visitorKey?: VisitorKey
88
+ rebuild?: boolean
89
+ }
90
+
91
+ /**
92
+ * Defines a node once and derives its `create` builder, `is` guard, and traversal
93
+ * metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
94
+ * `kind`, so node construction lives in one place without scattered `as` casts.
95
+ *
96
+ * Set `rebuild: true` when the `build` hook derives fields from children. After a
97
+ * transform rewrites those children, the registry reruns `create` so the derived
98
+ * fields stay correct.
99
+ *
100
+ * @example Simple node
101
+ * ```ts
102
+ * const importDef = defineNode<ImportNode>({ kind: 'Import' })
103
+ * const createImport = importDef.create
104
+ * ```
105
+ *
106
+ * @example Node with a build hook that is rerun on transform
107
+ * ```ts
108
+ * const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
109
+ * kind: 'Property',
110
+ * build: (props) => ({ ...props, required: props.required ?? false }),
111
+ * children: ['schema'],
112
+ * visitorKey: 'property',
113
+ * rebuild: true,
114
+ * })
115
+ * ```
116
+ */
117
+ export function defineNode<TNode extends BaseNode, TInput = Omit<TNode, 'kind'>, TBuilt extends object = Omit<TNode, 'kind'>>(
118
+ config: DefineNodeConfig<TNode, TInput, TBuilt>,
119
+ ): NodeDef<TNode, TInput> {
120
+ const { kind, defaults, build, children, visitorKey, rebuild } = config
121
+
122
+ function create(input: TInput): TNode {
123
+ const base = build ? build(input) : input
124
+ return { ...defaults, ...(base as object), kind } as TNode
125
+ }
126
+
127
+ return { kind, create, is: isKind<TNode>(kind), children, visitorKey, rebuild }
128
+ }
package/src/nodes/base.ts CHANGED
@@ -17,10 +17,11 @@ export type NodeKind =
17
17
  | 'RequestBody'
18
18
  | 'Content'
19
19
  | 'FunctionParameter'
20
- | 'ParameterGroup'
21
20
  | 'FunctionParameters'
21
+ | 'TypeLiteral'
22
+ | 'IndexedAccessType'
23
+ | 'ObjectBindingPattern'
22
24
  | 'Type'
23
- | 'ParamsType'
24
25
  | 'File'
25
26
  | 'Import'
26
27
  | 'Export'
package/src/nodes/code.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { defineNode } from '../node.ts'
1
2
  import type { BaseNode } from './base.ts'
2
3
 
3
4
  /**
@@ -296,3 +297,117 @@ export type JsxNode = BaseNode & {
296
297
  * structured children in {@link SourceNode.nodes}.
297
298
  */
298
299
  export type CodeNode = ConstNode | TypeNode | FunctionNode | ArrowFunctionNode | TextNode | BreakNode | JsxNode
300
+
301
+ /**
302
+ * Definition for the {@link ConstNode}.
303
+ */
304
+ export const constDef = defineNode<ConstNode>({ kind: 'Const' })
305
+
306
+ /**
307
+ * Creates a `ConstNode` representing a TypeScript `const` declaration.
308
+ *
309
+ * @example Exported constant with type and `as const`
310
+ * ```ts
311
+ * createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
312
+ * // export const pets: Pet[] = ... as const
313
+ * ```
314
+ */
315
+ export const createConst = constDef.create
316
+
317
+ /**
318
+ * Definition for the {@link TypeNode}.
319
+ */
320
+ export const typeDef = defineNode<TypeNode>({ kind: 'Type' })
321
+
322
+ /**
323
+ * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
324
+ *
325
+ * @example
326
+ * ```ts
327
+ * createType({ name: 'Pet', export: true })
328
+ * // export type Pet = ...
329
+ * ```
330
+ */
331
+ export const createType = typeDef.create
332
+
333
+ /**
334
+ * Definition for the {@link FunctionNode}.
335
+ */
336
+ export const functionDef = defineNode<FunctionNode>({ kind: 'Function' })
337
+
338
+ /**
339
+ * Creates a `FunctionNode` representing a TypeScript `function` declaration.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
344
+ * // export async function fetchPet(): Promise<Pet> { ... }
345
+ * ```
346
+ */
347
+ export const createFunction = functionDef.create
348
+
349
+ /**
350
+ * Definition for the {@link ArrowFunctionNode}.
351
+ */
352
+ export const arrowFunctionDef = defineNode<ArrowFunctionNode>({ kind: 'ArrowFunction' })
353
+
354
+ /**
355
+ * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
360
+ * // export const double = (n: number) => ...
361
+ * ```
362
+ */
363
+ export const createArrowFunction = arrowFunctionDef.create
364
+
365
+ /**
366
+ * Definition for the {@link TextNode}.
367
+ */
368
+ export const textDef = defineNode<TextNode, string>({ kind: 'Text', build: (value) => ({ value }) })
369
+
370
+ /**
371
+ * Creates a {@link TextNode} representing a raw string fragment in the source output.
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * createText('return fetch(id)')
376
+ * // { kind: 'Text', value: 'return fetch(id)' }
377
+ * ```
378
+ */
379
+ export const createText = textDef.create
380
+
381
+ /**
382
+ * Definition for the {@link BreakNode}.
383
+ */
384
+ export const breakDef = defineNode<BreakNode, void>({ kind: 'Break', build: () => ({}) })
385
+
386
+ /**
387
+ * Creates a {@link BreakNode} representing a line break in the source output.
388
+ *
389
+ * @example
390
+ * ```ts
391
+ * createBreak()
392
+ * // { kind: 'Break' }
393
+ * ```
394
+ */
395
+ export function createBreak(): BreakNode {
396
+ return breakDef.create()
397
+ }
398
+
399
+ /**
400
+ * Definition for the {@link JsxNode}.
401
+ */
402
+ export const jsxDef = defineNode<JsxNode, string>({ kind: 'Jsx', build: (value) => ({ value }) })
403
+
404
+ /**
405
+ * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * createJsx('<>\n <a href={href}>Open</a>\n</>')
410
+ * // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
411
+ * ```
412
+ */
413
+ export const createJsx = jsxDef.create
@@ -1,3 +1,4 @@
1
+ import { defineNode } from '../node.ts'
1
2
  import type { BaseNode } from './base.ts'
2
3
  import type { SchemaNode } from './schema.ts'
3
4
 
@@ -35,3 +36,21 @@ export type ContentNode = BaseNode & {
35
36
  */
36
37
  keysToOmit?: Array<string> | null
37
38
  }
39
+
40
+ /**
41
+ * Loosely-typed content entry accepted by the builders, normalized into a {@link ContentNode}.
42
+ */
43
+ export type UserContent = Omit<ContentNode, 'kind'>
44
+
45
+ /**
46
+ * Definition for the {@link ContentNode}.
47
+ */
48
+ export const contentDef = defineNode<ContentNode, UserContent>({
49
+ kind: 'Content',
50
+ children: ['schema'],
51
+ })
52
+
53
+ /**
54
+ * Creates a `ContentNode` for a single request-body or response content type.
55
+ */
56
+ export const createContent = contentDef.create
package/src/nodes/file.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { defineNode } from '../node.ts'
1
2
  import type { BaseNode } from './base.ts'
2
3
  import type { CodeNode } from './code.ts'
3
4
 
@@ -230,3 +231,56 @@ export type FileNode<TMeta extends object = object> = BaseNode & {
230
231
  */
231
232
  footer?: string | null
232
233
  }
234
+
235
+ /**
236
+ * Definition for the {@link ImportNode}.
237
+ */
238
+ export const importDef = defineNode<ImportNode>({ kind: 'Import' })
239
+
240
+ /**
241
+ * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
242
+ *
243
+ * @example Named import
244
+ * ```ts
245
+ * createImport({ name: ['useState'], path: 'react' })
246
+ * // import { useState } from 'react'
247
+ * ```
248
+ */
249
+ export const createImport = importDef.create
250
+
251
+ /**
252
+ * Definition for the {@link ExportNode}.
253
+ */
254
+ export const exportDef = defineNode<ExportNode>({ kind: 'Export' })
255
+
256
+ /**
257
+ * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
258
+ *
259
+ * @example Named export
260
+ * ```ts
261
+ * createExport({ name: ['Pet'], path: './Pet' })
262
+ * // export { Pet } from './Pet'
263
+ * ```
264
+ */
265
+ export const createExport = exportDef.create
266
+
267
+ /**
268
+ * Definition for the {@link SourceNode}.
269
+ */
270
+ export const sourceDef = defineNode<SourceNode>({ kind: 'Source' })
271
+
272
+ /**
273
+ * Creates a `SourceNode` representing a fragment of source code within a file.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
278
+ * ```
279
+ */
280
+ export const createSource = sourceDef.create
281
+
282
+ /**
283
+ * Definition for the {@link FileNode}. The fully resolved builder lives in
284
+ * `createFile`, so this definition only supplies the guard.
285
+ */
286
+ export const fileDef = defineNode<FileNode>({ kind: 'File' })