@kubb/ast 5.0.0-alpha.15 → 5.0.0-alpha.17

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/visitor.ts CHANGED
@@ -4,8 +4,20 @@ import { createParameter, createProperty } from './factory.ts'
4
4
  import type { Node, OperationNode, ParameterNode, PropertyNode, ResponseNode, RootNode, SchemaNode } from './nodes/index.ts'
5
5
 
6
6
  /**
7
- * Creates a concurrency-limiting wrapper. At most `concurrency` promises may be
8
- * in-flight simultaneously; additional calls are queued and dispatched as slots free.
7
+ * Creates a small async concurrency limiter.
8
+ *
9
+ * At most `concurrency` tasks are in flight at once. Extra tasks are queued.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const limit = createLimit(2)
14
+ * await Promise.all([
15
+ * limit(() => taskA()),
16
+ * limit(() => taskB()),
17
+ * limit(() => taskC()),
18
+ * ])
19
+ * // only 2 tasks run at the same time
20
+ * ```
9
21
  */
10
22
  function createLimit(concurrency: number) {
11
23
  let active = 0
@@ -36,10 +48,9 @@ function createLimit(concurrency: number) {
36
48
  type LimitFn = ReturnType<typeof createLimit>
37
49
 
38
50
  /**
39
- * Single source of truth: ordered list of `[NodeType, ParentType]` pairs
40
- * describing which node types can be the parent of a given node in the AST.
51
+ * Ordered mapping of `[NodeType, ParentType]` pairs.
41
52
  *
42
- * `ParentOf` walks this tuple and returns the parent type of the first matching entry.
53
+ * `ParentOf` uses this map to find parent types.
43
54
  */
44
55
  type ParentNodeMap = [
45
56
  [RootNode, undefined],
@@ -50,6 +61,30 @@ type ParentNodeMap = [
50
61
  [ResponseNode, OperationNode],
51
62
  ]
52
63
 
64
+ /**
65
+ * Resolves the parent node type for a given AST node type.
66
+ *
67
+ * This is used by visitor context so `ctx.parent` is correctly typed
68
+ * for each callback.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * type RootParent = ParentOf<RootNode>
73
+ * // undefined
74
+ * ```
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * type PropertyParent = ParentOf<PropertyNode>
79
+ * // SchemaNode
80
+ * ```
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * type SchemaParent = ParentOf<SchemaNode>
85
+ * // RootNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode
86
+ * ```
87
+ */
53
88
  export type ParentOf<T extends Node, TEntries extends ReadonlyArray<[Node, unknown]> = ParentNodeMap> = TEntries extends [
54
89
  infer TEntry extends [Node, unknown],
55
90
  ...infer TRest extends ReadonlyArray<[Node, unknown]>,
@@ -61,14 +96,36 @@ export type ParentOf<T extends Node, TEntries extends ReadonlyArray<[Node, unkno
61
96
 
62
97
  /**
63
98
  * Traversal context passed as the second argument to every visitor callback.
64
- * The `parent` field is narrowed based on the node type being visited.
99
+ * `parent` is typed from the current node type.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * const visitor: Visitor = {
104
+ * schema(node, { parent }) {
105
+ * // parent type is narrowed by node kind
106
+ * },
107
+ * }
108
+ * ```
65
109
  */
66
110
  export type VisitorContext<T extends Node = Node> = {
111
+ /**
112
+ * Parent node of the currently visited node.
113
+ * For `RootNode`, this is `undefined`.
114
+ */
67
115
  parent?: ParentOf<T>
68
116
  }
69
117
 
70
118
  /**
71
- * Synchronous visitor for `transform` and `walk`.
119
+ * Synchronous visitor used by `transform`.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const visitor: Visitor = {
124
+ * operation(node) {
125
+ * return { ...node, operationId: `x_${node.operationId}` }
126
+ * },
127
+ * }
128
+ * ```
72
129
  */
73
130
  export type Visitor = {
74
131
  root?(node: RootNode, context: VisitorContext<RootNode>): void | RootNode
@@ -79,10 +136,22 @@ export type Visitor = {
79
136
  response?(node: ResponseNode, context: VisitorContext<ResponseNode>): void | ResponseNode
80
137
  }
81
138
 
139
+ /**
140
+ * Utility type for values that can be returned directly or asynchronously.
141
+ */
82
142
  type MaybePromise<T> = T | Promise<T>
83
143
 
84
144
  /**
85
145
  * Async visitor for `walk`. Synchronous `Visitor` objects are compatible.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const visitor: AsyncVisitor = {
150
+ * async operation(node) {
151
+ * await Promise.resolve(node.operationId)
152
+ * },
153
+ * }
154
+ * ```
86
155
  */
87
156
  export type AsyncVisitor = {
88
157
  root?(node: RootNode, context: VisitorContext<RootNode>): MaybePromise<void | RootNode>
@@ -94,7 +163,16 @@ export type AsyncVisitor = {
94
163
  }
95
164
 
96
165
  /**
97
- * Visitor for `collect`.
166
+ * Visitor used by `collect`.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * const visitor: CollectVisitor<string> = {
171
+ * operation(node) {
172
+ * return node.operationId
173
+ * },
174
+ * }
175
+ * ```
98
176
  */
99
177
  export type CollectVisitor<T> = {
100
178
  root?(node: RootNode, context: VisitorContext<RootNode>): T | undefined
@@ -106,17 +184,44 @@ export type CollectVisitor<T> = {
106
184
  }
107
185
 
108
186
  /**
109
- * Options for `transform` and `collect`. Extends `Visitor` with traversal settings.
187
+ * Options for `transform`.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const options: TransformOptions = { depth: 'deep', schema: (node) => node }
192
+ * ```
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * // Only transform the current node, not nested children
197
+ * const options: TransformOptions = { depth: 'shallow', schema: (node) => node }
198
+ * ```
110
199
  */
111
200
  export type TransformOptions = Visitor & {
201
+ /**
202
+ * Traversal depth (`'deep'` by default).
203
+ * @default 'deep'
204
+ */
112
205
  depth?: VisitorDepth
206
+ /**
207
+ * Internal parent override used during recursion.
208
+ */
113
209
  parent?: Node
114
210
  }
115
211
 
116
212
  /**
117
- * Options for `walk`. Extends `AsyncVisitor` with traversal settings.
213
+ * Options for `walk`.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * const options: WalkOptions = { depth: 'deep', concurrency: 10, root: () => {} }
218
+ * ```
118
219
  */
119
220
  export type WalkOptions = AsyncVisitor & {
221
+ /**
222
+ * Traversal depth (`'deep'` by default).
223
+ * @default 'deep'
224
+ */
120
225
  depth?: VisitorDepth
121
226
  /**
122
227
  * Maximum number of sibling nodes visited concurrently.
@@ -126,18 +231,37 @@ export type WalkOptions = AsyncVisitor & {
126
231
  }
127
232
 
128
233
  /**
129
- * Options for `collect`. Extends `CollectVisitor` with traversal settings.
234
+ * Options for `collect`.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * const options: CollectOptions<string> = { depth: 'shallow', schema: () => undefined }
239
+ * ```
130
240
  */
131
241
  export type CollectOptions<T> = CollectVisitor<T> & {
242
+ /**
243
+ * Traversal depth (`'deep'` by default).
244
+ * @default 'deep'
245
+ */
132
246
  depth?: VisitorDepth
247
+ /**
248
+ * Internal parent override used during recursion.
249
+ */
133
250
  parent?: Node
134
251
  }
135
252
 
136
253
  /**
137
254
  * Returns the immediate traversable children of `node`.
138
255
  *
139
- * For `Schema` nodes, children (properties, items, members) are only included
140
- * when `recurse` is `true`; shallow traversal omits them entirely.
256
+ * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
257
+ * `additionalProperties`) are only included
258
+ * when `recurse` is `true`; shallow mode skips them.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * const children = getChildren(operationNode, true)
263
+ * // returns parameters, requestBody schema (if present), and responses
264
+ * ```
141
265
  */
142
266
  function getChildren(node: Node, recurse: boolean): Array<Node> {
143
267
  switch (node.kind) {
@@ -172,11 +296,28 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
172
296
 
173
297
  /**
174
298
  * Depth-first traversal for side effects. Visitor return values are ignored.
175
- * Sibling nodes at each level are visited concurrently up to `options.concurrency` (default: 30).
299
+ * Sibling nodes at each level are visited concurrently up to `options.concurrency`
300
+ * (default: `WALK_CONCURRENCY`).
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * await walk(root, {
305
+ * operation(node) {
306
+ * console.log(node.operationId)
307
+ * },
308
+ * })
309
+ * ```
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * // Visit only the current node
314
+ * await walk(root, { depth: 'shallow', root: () => {} })
315
+ * ```
176
316
  */
177
317
  export async function walk(node: Node, options: WalkOptions): Promise<void> {
178
318
  const recurse = (options.depth ?? visitorDepths.deep) === visitorDepths.deep
179
319
  const limit = createLimit(options.concurrency ?? WALK_CONCURRENCY)
320
+
180
321
  return _walk(node, options, recurse, limit, undefined)
181
322
  }
182
323
 
@@ -211,7 +352,25 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
211
352
  }
212
353
 
213
354
  /**
214
- * Depth-first immutable transformation. Visitor return values replace nodes; `undefined` keeps the original.
355
+ * Runs a depth-first immutable transform.
356
+ *
357
+ * If a visitor returns a node, it replaces the current node.
358
+ * If it returns `undefined`, the current node stays the same.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * const next = transform(root, {
363
+ * operation(node) {
364
+ * return { ...node, operationId: `prefixed_${node.operationId}` }
365
+ * },
366
+ * })
367
+ * ```
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * // Shallow mode: only transform the input node
372
+ * const next = transform(root, { depth: 'shallow', root: (node) => node })
373
+ * ```
215
374
  */
216
375
  export function transform(node: RootNode, options: TransformOptions): RootNode
217
376
  export function transform(node: OperationNode, options: TransformOptions): OperationNode
@@ -305,8 +464,18 @@ export function transform(node: Node, options: TransformOptions): Node {
305
464
  }
306
465
 
307
466
  /**
308
- * Combines multiple visitors into a single visitor that applies them sequentially (left to right).
309
- * For each node kind, the output of one visitor becomes the input of the next.
467
+ * Composes multiple visitors into one visitor, applied left to right.
468
+ *
469
+ * For each node kind, output from one visitor is input to the next.
470
+ * If a visitor returns `undefined`, the previous node value is kept.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * const visitor = composeTransformers(
475
+ * { operation: (node) => ({ ...node, operationId: `a_${node.operationId}` }) },
476
+ * { operation: (node) => ({ ...node, operationId: `b_${node.operationId}` }) },
477
+ * )
478
+ * ```
310
479
  */
311
480
  export function composeTransformers(...visitors: Array<Visitor>): Visitor {
312
481
  return {
@@ -332,7 +501,24 @@ export function composeTransformers(...visitors: Array<Visitor>): Visitor {
332
501
  }
333
502
 
334
503
  /**
335
- * Depth-first synchronous reduction. Collects non-`undefined` visitor return values into an array.
504
+ * Runs a depth-first synchronous collection pass.
505
+ *
506
+ * Non-`undefined` values returned by visitor callbacks are appended to the result.
507
+ *
508
+ * @example
509
+ * ```ts
510
+ * const ids = collect(root, {
511
+ * operation(node) {
512
+ * return node.operationId
513
+ * },
514
+ * })
515
+ * ```
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * // Collect from only the current node
520
+ * const values = collect(root, { depth: 'shallow', root: () => 'root' })
521
+ * ```
336
522
  */
337
523
  export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
338
524
  const { depth, parent, ...visitor } = options