@kubb/ast 5.0.0-beta.4 → 5.0.0-beta.40

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
@@ -1,7 +1,19 @@
1
1
  import type { VisitorDepth } from './constants.ts'
2
2
  import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
3
3
  import { createParameter, createProperty } from './factory.ts'
4
- import type { InputNode, Node, OperationNode, OutputNode, ParameterNode, PropertyNode, ResponseNode, SchemaNode } from './nodes/index.ts'
4
+ import type {
5
+ ContentNode,
6
+ InputNode,
7
+ Node,
8
+ NodeKind,
9
+ OperationNode,
10
+ OutputNode,
11
+ ParameterNode,
12
+ PropertyNode,
13
+ RequestBodyNode,
14
+ ResponseNode,
15
+ SchemaNode,
16
+ } from './nodes/index.ts'
5
17
 
6
18
  /**
7
19
  * Creates a small async concurrency limiter.
@@ -54,7 +66,9 @@ type ParentNodeMap = [
54
66
  [InputNode, undefined],
55
67
  [OutputNode, undefined],
56
68
  [OperationNode, InputNode],
57
- [SchemaNode, InputNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode],
69
+ [RequestBodyNode, OperationNode],
70
+ [ContentNode, RequestBodyNode | ResponseNode],
71
+ [SchemaNode, InputNode | ContentNode | SchemaNode | PropertyNode | ParameterNode],
58
72
  [PropertyNode, SchemaNode],
59
73
  [ParameterNode, OperationNode],
60
74
  [ResponseNode, OperationNode],
@@ -115,25 +129,40 @@ export type VisitorContext<T extends Node = Node> = {
115
129
  }
116
130
 
117
131
  /**
118
- * Synchronous visitor used by `transform`.
132
+ * Synchronous visitor consumed by `transform`. Each optional callback runs
133
+ * for the matching node type. Return a new node to replace it, or `undefined`
134
+ * to leave it untouched.
119
135
  *
120
- * @example
136
+ * Plugins typically expose `transformer` so users can supply a `Visitor` that
137
+ * rewrites operation IDs, drops descriptions, or otherwise tweaks the AST
138
+ * before printing.
139
+ *
140
+ * @example Prefix every operationId
121
141
  * ```ts
122
142
  * const visitor: Visitor = {
123
143
  * operation(node) {
124
- * return { ...node, operationId: `x_${node.operationId}` }
144
+ * return { ...node, operationId: `api_${node.operationId}` }
145
+ * },
146
+ * }
147
+ * ```
148
+ *
149
+ * @example Strip schema descriptions
150
+ * ```ts
151
+ * const visitor: Visitor = {
152
+ * schema(node) {
153
+ * return { ...node, description: undefined }
125
154
  * },
126
155
  * }
127
156
  * ```
128
157
  */
129
158
  export type Visitor = {
130
- input?(node: InputNode, context: VisitorContext<InputNode>): void | InputNode
131
- output?(node: OutputNode, context: VisitorContext<OutputNode>): void | OutputNode
132
- operation?(node: OperationNode, context: VisitorContext<OperationNode>): void | OperationNode
133
- schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): void | SchemaNode
134
- property?(node: PropertyNode, context: VisitorContext<PropertyNode>): void | PropertyNode
135
- parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): void | ParameterNode
136
- response?(node: ResponseNode, context: VisitorContext<ResponseNode>): void | ResponseNode
159
+ input?(node: InputNode, context: VisitorContext<InputNode>): undefined | null | InputNode
160
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): undefined | null | OutputNode
161
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): undefined | null | OperationNode
162
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): undefined | null | SchemaNode
163
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): undefined | null | PropertyNode
164
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): undefined | null | ParameterNode
165
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): undefined | null | ResponseNode
137
166
  }
138
167
 
139
168
  /**
@@ -154,13 +183,13 @@ type MaybePromise<T> = T | Promise<T>
154
183
  * ```
155
184
  */
156
185
  export type AsyncVisitor = {
157
- input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<void | InputNode>
158
- output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<void | OutputNode>
159
- operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<void | OperationNode>
160
- schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<void | SchemaNode>
161
- property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<void | PropertyNode>
162
- parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<void | ParameterNode>
163
- response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<void | ResponseNode>
186
+ input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<undefined | null | InputNode>
187
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<undefined | null | OutputNode>
188
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<undefined | null | OperationNode>
189
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<undefined | null | SchemaNode>
190
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<undefined | null | PropertyNode>
191
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<undefined | null | ParameterNode>
192
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<undefined | null | ResponseNode>
164
193
  }
165
194
 
166
195
  /**
@@ -176,13 +205,13 @@ export type AsyncVisitor = {
176
205
  * ```
177
206
  */
178
207
  export type CollectVisitor<T> = {
179
- input?(node: InputNode, context: VisitorContext<InputNode>): T | undefined
180
- output?(node: OutputNode, context: VisitorContext<OutputNode>): T | undefined
181
- operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | undefined
182
- schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | undefined
183
- property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | undefined
184
- parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | undefined
185
- response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | undefined
208
+ input?(node: InputNode, context: VisitorContext<InputNode>): T | null | undefined
209
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): T | null | undefined
210
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | null | undefined
211
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | null | undefined
212
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | null | undefined
213
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | null | undefined
214
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | null | undefined
186
215
  }
187
216
 
188
217
  /**
@@ -253,60 +282,102 @@ export type CollectOptions<T> = CollectVisitor<T> & {
253
282
  }
254
283
 
255
284
  /**
256
- * Returns the immediate traversable children of `node`.
285
+ * Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
286
+ *
287
+ * Each listed property holds a child node, an array of child nodes, or, for
288
+ * `additionalProperties` a node or the literal `true` (skipped). Every value
289
+ * in a child slot is a node, so one table drives both `getChildren` and `transform`.
290
+ */
291
+ const VISITOR_KEYS = {
292
+ Input: ['schemas', 'operations'],
293
+ Operation: ['parameters', 'requestBody', 'responses'],
294
+ RequestBody: ['content'],
295
+ Content: ['schema'],
296
+ Response: ['content'],
297
+ Schema: ['properties', 'items', 'members', 'additionalProperties'],
298
+ Property: ['schema'],
299
+ Parameter: ['schema'],
300
+ } as const satisfies Partial<Record<NodeKind, ReadonlyArray<string>>>
301
+
302
+ const visitorKeysByKind = VISITOR_KEYS as Record<string, ReadonlyArray<string> | undefined>
303
+
304
+ /**
305
+ * Returns `true` when `value` is an AST node (an object carrying a `kind`).
306
+ */
307
+ function isNode(value: unknown): value is Node {
308
+ return typeof value === 'object' && value !== null && 'kind' in value
309
+ }
310
+
311
+ /**
312
+ * Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
257
313
  *
258
- * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
259
- * `additionalProperties`) are only included
260
- * when `recurse` is `true`; shallow mode skips them.
314
+ * `Schema` children are only included when `recurse` is `true`. Shallow mode skips them.
261
315
  *
262
316
  * @example
263
317
  * ```ts
264
318
  * const children = getChildren(operationNode, true)
265
- * // returns parameters, requestBody schema (if present), and responses
319
+ * // returns parameters, the request body, and responses
266
320
  * ```
267
321
  */
268
- function getChildren(node: Node, recurse: boolean): Array<Node> {
269
- switch (node.kind) {
270
- case 'Input':
271
- return [...node.schemas, ...node.operations]
272
- case 'Output':
273
- return []
274
- case 'Operation':
275
- return [...node.parameters, ...(node.requestBody?.content?.flatMap((c) => (c.schema ? [c.schema] : [])) ?? []), ...node.responses]
276
- case 'Schema': {
277
- const children: Array<Node> = []
278
-
279
- if (!recurse) return []
280
-
281
- if ('properties' in node && node.properties.length > 0) children.push(...node.properties)
282
- if ('items' in node && node.items) children.push(...node.items)
283
- if ('members' in node && node.members) children.push(...node.members)
284
- if ('additionalProperties' in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties)
285
-
286
- return children
322
+ function* getChildren(node: Node, recurse: boolean): Generator<Node, void, undefined> {
323
+ if (node.kind === 'Schema' && !recurse) return
324
+
325
+ const keys = visitorKeysByKind[node.kind]
326
+ if (!keys) return
327
+
328
+ const record = node as unknown as Record<string, unknown>
329
+ for (const key of keys) {
330
+ const value = record[key]
331
+ if (Array.isArray(value)) {
332
+ for (const item of value) if (isNode(item)) yield item
333
+ } else if (isNode(value)) {
334
+ yield value
287
335
  }
288
- case 'Property':
289
- return [node.schema]
290
- case 'Parameter':
291
- return [node.schema]
292
- case 'Response':
293
- return node.schema ? [node.schema] : []
294
- case 'FunctionParameter':
295
- case 'ParameterGroup':
296
- case 'FunctionParameters':
297
- case 'Type':
298
- return []
299
- default:
300
- return []
301
336
  }
302
337
  }
303
338
 
304
339
  /**
305
- * Depth-first traversal for side effects. Visitor return values are ignored.
306
- * Sibling nodes at each level are visited concurrently up to `options.concurrency`
307
- * (default: `WALK_CONCURRENCY`).
340
+ * Maps a node `kind` to the matching visitor callback name. Only the seven
341
+ * traversable node kinds have an entry. Every other kind resolves to
342
+ * `undefined` and is skipped.
343
+ */
344
+ const VISITOR_KEY_BY_KIND: Partial<Record<NodeKind, keyof Visitor>> = {
345
+ Input: 'input',
346
+ Output: 'output',
347
+ Operation: 'operation',
348
+ Schema: 'schema',
349
+ Property: 'property',
350
+ Parameter: 'parameter',
351
+ Response: 'response',
352
+ }
353
+
354
+ /**
355
+ * Invokes the visitor callback that matches `node.kind`, passing the traversal
356
+ * context. Returns the callback's result (a replacement node, a collected
357
+ * value, or `undefined` when no callback is registered for the kind).
308
358
  *
309
- * @example
359
+ * Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
360
+ * in one place. `TResult` is the caller's expected return: the same node type
361
+ * for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
362
+ */
363
+ function applyVisitor<TResult>(node: Node, visitor: Visitor | AsyncVisitor | CollectVisitor<unknown>, parent: Node | undefined): TResult | null | undefined {
364
+ const key = VISITOR_KEY_BY_KIND[node.kind]
365
+ if (!key) return undefined
366
+
367
+ const fn = visitor[key] as ((node: Node, context: VisitorContext) => TResult | null | undefined) | undefined
368
+
369
+ return fn?.(node, { parent })
370
+ }
371
+
372
+ /**
373
+ * Async depth-first traversal for side effects. Visitor return values are
374
+ * ignored. Use `transform` when you want to rewrite nodes.
375
+ *
376
+ * Sibling nodes at each depth run concurrently up to `options.concurrency`
377
+ * (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
378
+ * work. Lower values reduce memory pressure.
379
+ *
380
+ * @example Log every operation
310
381
  * ```ts
311
382
  * await walk(root, {
312
383
  * operation(node) {
@@ -315,10 +386,9 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
315
386
  * })
316
387
  * ```
317
388
  *
318
- * @example
389
+ * @example Only visit the root node
319
390
  * ```ts
320
- * // Visit only the current node
321
- * await walk(root, { depth: 'shallow', root: () => {} })
391
+ * await walk(root, { depth: 'shallow', input: () => {} })
322
392
  * ```
323
393
  */
324
394
  export async function walk(node: Node, options: WalkOptions): Promise<void> {
@@ -329,55 +399,26 @@ export async function walk(node: Node, options: WalkOptions): Promise<void> {
329
399
  }
330
400
 
331
401
  async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn, parent: Node | undefined): Promise<void> {
332
- switch (node.kind) {
333
- case 'Input':
334
- await limit(() => visitor.input?.(node, { parent: parent as ParentOf<InputNode> }))
335
- break
336
- case 'Output':
337
- await limit(() => visitor.output?.(node, { parent: parent as ParentOf<OutputNode> }))
338
- break
339
- case 'Operation':
340
- await limit(() =>
341
- visitor.operation?.(node, {
342
- parent: parent as ParentOf<OperationNode>,
343
- }),
344
- )
345
- break
346
- case 'Schema':
347
- await limit(() => visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> }))
348
- break
349
- case 'Property':
350
- await limit(() => visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> }))
351
- break
352
- case 'Parameter':
353
- await limit(() =>
354
- visitor.parameter?.(node, {
355
- parent: parent as ParentOf<ParameterNode>,
356
- }),
357
- )
358
- break
359
- case 'Response':
360
- await limit(() => visitor.response?.(node, { parent: parent as ParentOf<ResponseNode> }))
361
- break
362
- case 'FunctionParameter':
363
- case 'ParameterGroup':
364
- case 'FunctionParameters':
365
- break
366
- }
402
+ await limit(() => applyVisitor(node, visitor, parent))
367
403
 
368
- const children = getChildren(node, recurse)
369
- for (const child of children) {
370
- await _walk(child, visitor, recurse, limit, node)
371
- }
404
+ // Visit siblings concurrently and let the shared `limit` cap how many callbacks
405
+ // run at once. Awaiting each child sequentially here would serialize the whole
406
+ // traversal and make `concurrency` inert, every visitor callback would run one
407
+ // at a time regardless of the limit.
408
+ const children = Array.from(getChildren(node, recurse))
409
+ if (children.length === 0) return
410
+
411
+ await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)))
372
412
  }
373
413
 
374
414
  /**
375
- * Runs a depth-first immutable transform.
415
+ * Synchronous depth-first transform. Each visitor callback gets a chance to
416
+ * return a replacement node; `undefined` keeps the original.
376
417
  *
377
- * If a visitor returns a node, it replaces the current node.
378
- * If it returns `undefined`, the current node stays the same.
418
+ * The transform is immutable. The original tree is not mutated. A new tree
419
+ * is returned. Use `depth: 'shallow'` to skip recursion into children.
379
420
  *
380
- * @example
421
+ * @example Prefix every operationId
381
422
  * ```ts
382
423
  * const next = transform(root, {
383
424
  * operation(node) {
@@ -386,10 +427,12 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
386
427
  * })
387
428
  * ```
388
429
  *
389
- * @example
430
+ * @example Replace only the root node
390
431
  * ```ts
391
- * // Shallow mode: only transform the input node
392
- * const next = transform(root, { depth: 'shallow', root: (node) => node })
432
+ * const next = transform(root, {
433
+ * depth: 'shallow',
434
+ * input: (node) => ({ ...node, meta: { ...node.meta, title: 'Rewritten' } }),
435
+ * })
393
436
  * ```
394
437
  */
395
438
  export function transform(node: InputNode, options: TransformOptions): InputNode
@@ -404,189 +447,104 @@ export function transform(node: Node, options: TransformOptions): Node {
404
447
  const { depth, parent, ...visitor } = options
405
448
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
406
449
 
407
- switch (node.kind) {
408
- case 'Input': {
409
- let input = node
410
- const replaced = visitor.input?.(input, {
411
- parent: parent as ParentOf<InputNode>,
412
- })
413
- if (replaced) input = replaced
450
+ const visited = applyVisitor<Node>(node, visitor, parent) ?? node
451
+ const rebuilt = transformChildren(visited, options, recurse)
414
452
 
415
- return {
416
- ...input,
417
- schemas: input.schemas.map((s) => transform(s, { ...options, parent: input })),
418
- operations: input.operations.map((op) => transform(op, { ...options, parent: input })),
419
- }
420
- }
421
- case 'Output': {
422
- let output = node
423
- const replaced = visitor.output?.(output, {
424
- parent: parent as ParentOf<OutputNode>,
425
- })
426
- if (replaced) output = replaced
453
+ // Structural sharing: when the visitor and child rebuild both left this node
454
+ // untouched, return the original reference so callers can detect "nothing
455
+ // changed" by identity and ancestors can avoid reallocating.
456
+ if (rebuilt === node) return node
427
457
 
428
- return output
429
- }
430
- case 'Operation': {
431
- let op = node
432
- const replaced = visitor.operation?.(op, {
433
- parent: parent as ParentOf<OperationNode>,
434
- })
435
- if (replaced) op = replaced
436
-
437
- return {
438
- ...op,
439
- parameters: op.parameters.map((p) => transform(p, { ...options, parent: op })),
440
- requestBody: op.requestBody
441
- ? {
442
- ...op.requestBody,
443
- content: op.requestBody.content?.map((c) => ({
444
- ...c,
445
- schema: c.schema ? transform(c.schema, { ...options, parent: op }) : undefined,
446
- })),
447
- }
448
- : undefined,
449
- responses: op.responses.map((r) => transform(r, { ...options, parent: op })),
450
- }
451
- }
452
- case 'Schema': {
453
- let schema = node
454
- const replaced = visitor.schema?.(schema, {
455
- parent: parent as ParentOf<SchemaNode>,
456
- })
457
- if (replaced) schema = replaced
458
-
459
- const childOptions = { ...options, parent: schema }
460
-
461
- return {
462
- ...schema,
463
- ...('properties' in schema && recurse
464
- ? {
465
- properties: schema.properties.map((p) => transform(p, childOptions)),
466
- }
467
- : {}),
468
- ...('items' in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {}),
469
- ...('members' in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {}),
470
- ...('additionalProperties' in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true
471
- ? {
472
- additionalProperties: transform(schema.additionalProperties, childOptions),
473
- }
474
- : {}),
475
- } as SchemaNode
476
- }
477
- case 'Property': {
478
- let prop = node
479
- const replaced = visitor.property?.(prop, {
480
- parent: parent as ParentOf<PropertyNode>,
481
- })
482
- if (replaced) prop = replaced
458
+ const finalize = nodeFinalizers[rebuilt.kind]
459
+ return finalize ? finalize(rebuilt) : rebuilt
460
+ }
483
461
 
484
- return createProperty({
485
- ...prop,
486
- schema: transform(prop.schema, { ...options, parent: prop }),
487
- })
488
- }
489
- case 'Parameter': {
490
- let param = node
491
- const replaced = visitor.parameter?.(param, {
492
- parent: parent as ParentOf<ParameterNode>,
493
- })
494
- if (replaced) param = replaced
462
+ /**
463
+ * Per-kind builders rerun after children are rebuilt. `Property`/`Parameter`
464
+ * resync schema optionality against their `required` flag once the schema may
465
+ * have changed.
466
+ */
467
+ const nodeFinalizers: Partial<Record<NodeKind, (node: Node) => Node>> = {
468
+ Property: (node) => createProperty(node as PropertyNode),
469
+ Parameter: (node) => createParameter(node as ParameterNode),
470
+ }
495
471
 
496
- return createParameter({
497
- ...param,
498
- schema: transform(param.schema, { ...options, parent: param }),
499
- })
500
- }
501
- case 'Response': {
502
- let response = node
503
- const replaced = visitor.response?.(response, {
504
- parent: parent as ParentOf<ResponseNode>,
472
+ /**
473
+ * Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
474
+ * each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
475
+ * `Schema` children are skipped in shallow mode.
476
+ */
477
+ function transformChildren(node: Node, options: TransformOptions, recurse: boolean): Node {
478
+ if (node.kind === 'Schema' && !recurse) return node
479
+
480
+ const keys = visitorKeysByKind[node.kind]
481
+ if (!keys) return node
482
+
483
+ const record = node as unknown as Record<string, unknown>
484
+ const childOptions = { ...options, parent: node }
485
+ let updates: Record<string, unknown> | undefined
486
+
487
+ for (const key of keys) {
488
+ if (!(key in record)) continue
489
+ const value = record[key]
490
+ if (Array.isArray(value)) {
491
+ let changed = false
492
+ const mapped = value.map((item) => {
493
+ if (!isNode(item)) return item
494
+ const next = transform(item, childOptions)
495
+ if (next !== item) changed = true
496
+ return next
505
497
  })
506
- if (replaced) response = replaced
507
-
508
- return {
509
- ...response,
510
- schema: transform(response.schema, { ...options, parent: response }),
511
- }
498
+ if (changed) (updates ??= {})[key] = mapped
499
+ } else if (isNode(value)) {
500
+ const next = transform(value, childOptions)
501
+ if (next !== value) (updates ??= {})[key] = next
512
502
  }
513
- case 'FunctionParameter':
514
- case 'ParameterGroup':
515
- case 'FunctionParameters':
516
- case 'Type':
517
- return node
518
- default:
519
- return node
520
503
  }
504
+
505
+ return updates ? ({ ...node, ...updates } as Node) : node
521
506
  }
522
507
  /**
523
- * Runs a depth-first synchronous collection pass.
524
- *
525
- * Non-`undefined` values returned by visitor callbacks are appended to the result.
508
+ * Lazy depth-first collection pass. Yields every non-null value returned by
509
+ * the visitor callbacks. Use `collect` for the eager array form.
526
510
  *
527
- * @example
511
+ * @example Collect every operationId
528
512
  * ```ts
529
- * const ids = collect(root, {
513
+ * const ids: string[] = []
514
+ * for (const id of collectLazy<string>(root, {
530
515
  * operation(node) {
531
516
  * return node.operationId
532
517
  * },
533
- * })
534
- * ```
535
- *
536
- * @example
537
- * ```ts
538
- * // Collect from only the current node
539
- * const values = collect(root, { depth: 'shallow', root: () => 'root' })
518
+ * })) {
519
+ * ids.push(id)
520
+ * }
540
521
  * ```
541
522
  */
542
- export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
523
+ export function* collectLazy<T>(node: Node, options: CollectOptions<T>): Generator<T, void, undefined> {
543
524
  const { depth, parent, ...visitor } = options
544
525
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
545
- const results: Array<T> = []
546
-
547
- let v: T | undefined
548
- switch (node.kind) {
549
- case 'Input':
550
- v = visitor.input?.(node, { parent: parent as ParentOf<InputNode> })
551
- break
552
- case 'Output':
553
- v = visitor.output?.(node, { parent: parent as ParentOf<OutputNode> })
554
- break
555
- case 'Operation':
556
- v = visitor.operation?.(node, {
557
- parent: parent as ParentOf<OperationNode>,
558
- })
559
- break
560
- case 'Schema':
561
- v = visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> })
562
- break
563
- case 'Property':
564
- v = visitor.property?.(node, {
565
- parent: parent as ParentOf<PropertyNode>,
566
- })
567
- break
568
- case 'Parameter':
569
- v = visitor.parameter?.(node, {
570
- parent: parent as ParentOf<ParameterNode>,
571
- })
572
- break
573
- case 'Response':
574
- v = visitor.response?.(node, {
575
- parent: parent as ParentOf<ResponseNode>,
576
- })
577
- break
578
- case 'FunctionParameter':
579
- case 'ParameterGroup':
580
- case 'FunctionParameters':
581
- break
582
- }
583
- if (v !== undefined) results.push(v)
526
+
527
+ const v = applyVisitor<T>(node, visitor, parent)
528
+ if (v != null) yield v
584
529
 
585
530
  for (const child of getChildren(node, recurse)) {
586
- for (const item of collect(child, { ...options, parent: node })) {
587
- results.push(item)
588
- }
531
+ yield* collectLazy(child, { ...options, parent: node })
589
532
  }
533
+ }
590
534
 
591
- return results
535
+ /**
536
+ * Eager depth-first collection pass. Returns an array of every non-null value
537
+ * the visitor callbacks return.
538
+ *
539
+ * @example Collect every operationId
540
+ * ```ts
541
+ * const ids = collect<string>(root, {
542
+ * operation(node) {
543
+ * return node.operationId
544
+ * },
545
+ * })
546
+ * ```
547
+ */
548
+ export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
549
+ return Array.from(collectLazy(node, options))
592
550
  }
File without changes