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

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.
Files changed (59) hide show
  1. package/README.md +13 -9
  2. package/dist/{types-BL7RpQAE.d.ts → ast-ClnJg9BN.d.ts} +1630 -2443
  3. package/dist/chunk-CNktS9qV.js +17 -0
  4. package/dist/extractStringsFromNodes-Bn9cOos9.d.ts +14 -0
  5. package/dist/factory-C5gHvtLU.js +138 -0
  6. package/dist/factory-C5gHvtLU.js.map +1 -0
  7. package/dist/factory-JN-Ylfl6.cjs +155 -0
  8. package/dist/factory-JN-Ylfl6.cjs.map +1 -0
  9. package/dist/factory.cjs +31 -0
  10. package/dist/factory.d.ts +62 -0
  11. package/dist/factory.js +3 -0
  12. package/dist/index.cjs +56 -1751
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +6 -47
  15. package/dist/index.js +7 -1697
  16. package/dist/index.js.map +1 -1
  17. package/dist/types-CB2oY8Dw.d.ts +769 -0
  18. package/dist/types.d.ts +4 -2
  19. package/dist/utils-C8bWAzhv.cjs +2696 -0
  20. package/dist/utils-C8bWAzhv.cjs.map +1 -0
  21. package/dist/utils-DN4XLVqz.js +2099 -0
  22. package/dist/utils-DN4XLVqz.js.map +1 -0
  23. package/dist/utils.cjs +12 -1
  24. package/dist/utils.d.ts +21 -2
  25. package/dist/utils.js +2 -2
  26. package/package.json +5 -1
  27. package/src/dedupe.ts +1 -1
  28. package/src/factory.ts +22 -764
  29. package/src/guards.ts +1 -53
  30. package/src/index.ts +20 -39
  31. package/src/mocks.ts +6 -1
  32. package/src/node.ts +128 -0
  33. package/src/nodes/base.ts +3 -12
  34. package/src/nodes/code.ts +115 -0
  35. package/src/nodes/content.ts +19 -0
  36. package/src/nodes/file.ts +54 -0
  37. package/src/nodes/function.ts +222 -147
  38. package/src/nodes/index.ts +11 -3
  39. package/src/nodes/input.ts +37 -0
  40. package/src/nodes/operation.ts +59 -1
  41. package/src/nodes/output.ts +23 -0
  42. package/src/nodes/parameter.ts +33 -0
  43. package/src/nodes/property.ts +36 -0
  44. package/src/nodes/requestBody.ts +23 -1
  45. package/src/nodes/response.ts +39 -1
  46. package/src/nodes/schema.ts +72 -0
  47. package/src/printer.ts +3 -3
  48. package/src/registry.ts +70 -0
  49. package/src/transformers.ts +2 -2
  50. package/src/types.ts +6 -4
  51. package/src/utils/ast.ts +103 -243
  52. package/src/utils/extractStringsFromNodes.ts +34 -0
  53. package/src/utils/index.ts +44 -0
  54. package/src/visitor.ts +3 -47
  55. package/dist/chunk-C0LytTxp.js +0 -8
  56. package/dist/utils-0p8ZO287.js +0 -570
  57. package/dist/utils-0p8ZO287.js.map +0 -1
  58. package/dist/utils-cdQ6Pzyi.cjs +0 -726
  59. package/dist/utils-cdQ6Pzyi.cjs.map +0 -1
package/src/utils/ast.ts CHANGED
@@ -1,22 +1,23 @@
1
1
  import { camelCase, isValidVarName, memoize } from '@internals/utils'
2
2
 
3
- import { createFunctionParameter, createFunctionParameters, createParameterGroup, createParamsType, createProperty, createSchema } from '../factory.ts'
4
3
  import { narrowSchema } from '../guards.ts'
4
+ import { createFunctionParameter, createFunctionParameters, createIndexedAccessType, createTypeLiteral } from '../nodes/function.ts'
5
+ import { createProperty } from '../nodes/property.ts'
6
+ import { createSchema } from '../nodes/schema.ts'
5
7
  import type {
6
- CodeNode,
7
8
  ExportNode,
8
9
  FunctionParameterNode,
9
10
  FunctionParametersNode,
10
11
  ImportNode,
11
12
  OperationNode,
12
- ParameterGroupNode,
13
13
  ParameterNode,
14
- ParamsTypeNode,
15
14
  SchemaNode,
16
15
  SourceNode,
16
+ TypeExpression,
17
+ TypeLiteralNode,
17
18
  } from '../nodes/index.ts'
18
19
  import type { SchemaType } from '../nodes/schema.ts'
19
- import { extractRefName } from './index.ts'
20
+ import { extractRefName, extractStringsFromNodes, resolveGroupType } from './index.ts'
20
21
  import { collect, collectLazy } from '../visitor.ts'
21
22
 
22
23
  const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'] as const)
@@ -118,17 +119,26 @@ export function createDiscriminantNode({ propertyName, value }: { propertyName:
118
119
  /**
119
120
  * Named type for a group of parameters (query or header) emitted as a single typed parameter.
120
121
  */
121
- type ParamGroupType = {
122
+ export type ParamGroupType = {
122
123
  /**
123
- * TypeNode for the group type.
124
+ * Type expression for the group, a plain group-name reference.
124
125
  */
125
- type: ParamsTypeNode
126
+ type: TypeExpression
126
127
  /**
127
128
  * Whether the parameter group is optional.
128
129
  */
129
130
  optional: boolean
130
131
  }
131
132
 
133
+ /**
134
+ * A single member of a destructured parameter group, fed to `createFunctionParameter({ properties })`.
135
+ */
136
+ type GroupProperty = {
137
+ name: string
138
+ type: TypeExpression
139
+ optional?: boolean
140
+ }
141
+
132
142
  /**
133
143
  * Resolver interface for {@link createOperationParams}.
134
144
  *
@@ -215,7 +225,7 @@ export type CreateOperationParamsOptions = {
215
225
  * extraParams: [createFunctionParameter({ name: 'options', type: 'Partial<RequestOptions>', default: '{}' })]
216
226
  * ```
217
227
  */
218
- extraParams?: Array<FunctionParameterNode | ParameterGroupNode>
228
+ extraParams?: Array<FunctionParameterNode>
219
229
  /**
220
230
  * Override the default parameter names used for body, query, header, and rest-path groups.
221
231
  *
@@ -255,7 +265,14 @@ export type CreateOperationParamsOptions = {
255
265
  typeWrapper?: (type: string) => string
256
266
  }
257
267
 
258
- function resolveParamsType({
268
+ /**
269
+ * Resolves the {@link TypeExpression} for an individual parameter.
270
+ *
271
+ * Without a resolver, falls back to the schema primitive (a plain type-name string).
272
+ * When the parameter belongs to a named group, emits an {@link IndexedAccessTypeNode}
273
+ * (`GroupParams['petId']`); otherwise the resolved individual name as a plain string.
274
+ */
275
+ export function resolveParamType({
259
276
  node,
260
277
  param,
261
278
  resolver,
@@ -263,12 +280,9 @@ function resolveParamsType({
263
280
  node: OperationNode
264
281
  param: ParameterNode
265
282
  resolver: OperationParamsResolver | undefined
266
- }): ParamsTypeNode {
283
+ }): TypeExpression {
267
284
  if (!resolver) {
268
- return createParamsType({
269
- variant: 'reference',
270
- name: param.schema.primitive ?? 'unknown',
271
- })
285
+ return param.schema.primitive ?? 'unknown'
272
286
  }
273
287
 
274
288
  const individualName = resolver.resolveParamName(node, param)
@@ -284,23 +298,19 @@ function resolveParamsType({
284
298
  const groupName = groupLocation ? groupResolvers[groupLocation].call(resolver, node, param) : undefined
285
299
 
286
300
  if (groupName && groupName !== individualName) {
287
- return createParamsType({
288
- variant: 'member',
289
- base: groupName,
290
- key: param.name,
291
- })
301
+ return createIndexedAccessType({ objectType: groupName, indexType: param.name })
292
302
  }
293
303
 
294
- return createParamsType({ variant: 'reference', name: individualName })
304
+ return individualName
295
305
  }
296
306
 
297
307
  /**
298
308
  * Converts an `OperationNode` into function parameters for code generation.
299
309
  *
300
- * Centralizes parameter grouping logic for all plugins. Provide a `resolver` for type name resolution
301
- * and `extraParams` for plugin-specific trailing parameters (e.g., `options` objects).
302
- * Supports three grouping modes: `object` (single destructured param), `inline` (separate params),
303
- * and `inlineSpread` (rest parameter). Use `CreateOperationParamsOptions` to fine-tune output.
310
+ * Centralizes parameter grouping logic for all plugins. `paramsType` chooses between one
311
+ * destructured object parameter (`object`) and separate top-level parameters (`inline`), while
312
+ * `pathParamsType` controls how path params render in inline mode. Provide a `resolver` for type
313
+ * name resolution and `extraParams` for plugin-specific trailing parameters such as an `options` object.
304
314
  */
305
315
  export function createOperationParams(node: OperationNode, options: CreateOperationParamsOptions): FunctionParametersNode {
306
316
  const { paramsType, pathParamsType, paramsCasing, resolver, pathParamsDefault, extraParams = [], paramNames, typeWrapper } = options
@@ -310,147 +320,58 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
310
320
  const headersName = paramNames?.headers ?? 'headers'
311
321
  const pathName = paramNames?.path ?? 'pathParams'
312
322
 
313
- const wrapType = (type: string): ParamsTypeNode =>
314
- createParamsType({
315
- variant: 'reference',
316
- name: typeWrapper ? typeWrapper(type) : type,
317
- })
318
- // Only reference-variant TypeNodes are wrapped, they hold a plain type name string that needs casing applied.
319
- // Member and struct TypeNodes are pre-resolved structured expressions and are passed through unchanged.
320
- const wrapTypeNode = (type: ParamsTypeNode): ParamsTypeNode => (type.kind === 'ParamsType' && type.variant === 'reference' ? wrapType(type.name) : type)
323
+ const wrapType = (type: string): string => (typeWrapper ? typeWrapper(type) : type)
324
+ // Only plain type-name references are wrapped, they need casing applied.
325
+ // TypeLiteral and IndexedAccessType expressions are pre-resolved and pass through unchanged.
326
+ const wrapTypeExpression = (type: TypeExpression): TypeExpression => (typeof type === 'string' ? wrapType(type) : type)
321
327
 
322
328
  const casedParams = caseParams(node.parameters, paramsCasing)
323
329
  const pathParams = casedParams.filter((p) => p.in === 'path')
324
330
  const queryParams = casedParams.filter((p) => p.in === 'query')
325
331
  const headerParams = casedParams.filter((p) => p.in === 'header')
326
332
 
333
+ const toProperty = (param: ParameterNode): GroupProperty => ({
334
+ name: param.name,
335
+ type: wrapTypeExpression(resolveParamType({ node, param, resolver })),
336
+ optional: !param.required,
337
+ })
338
+ const emptyObjectDefault = (props: Array<GroupProperty>): string | undefined => (props.every((p) => p.optional) ? '{}' : undefined)
339
+
327
340
  const bodyType = node.requestBody?.content?.[0]?.schema ? wrapType(resolver?.resolveDataName(node) ?? 'unknown') : undefined
328
- const bodyRequired = node.requestBody?.required ?? false
329
-
330
- const queryGroupType = resolver
331
- ? resolveGroupType({
332
- node,
333
- params: queryParams,
334
- groupMethod: resolver.resolveQueryParamsName,
335
- resolver,
336
- })
337
- : undefined
338
- const headerGroupType = resolver
339
- ? resolveGroupType({
340
- node,
341
- params: headerParams,
342
- groupMethod: resolver.resolveHeaderParamsName,
343
- resolver,
344
- })
345
- : undefined
346
-
347
- const params: Array<FunctionParameterNode | ParameterGroupNode> = []
341
+ const bodyProperty: Array<GroupProperty> = bodyType ? [{ name: dataName, type: bodyType, optional: !(node.requestBody?.required ?? false) }] : []
342
+
343
+ const trailingGroups: Array<BuildGroupArgs> = [
344
+ { name: paramsName, node, params: queryParams, groupType: resolveGroupType({ node, params: queryParams, group: 'query', resolver }), resolver, wrapType },
345
+ {
346
+ name: headersName,
347
+ node,
348
+ params: headerParams,
349
+ groupType: resolveGroupType({ node, params: headerParams, group: 'header', resolver }),
350
+ resolver,
351
+ wrapType,
352
+ },
353
+ ]
348
354
 
349
- if (paramsType === 'object') {
350
- const children: Array<FunctionParameterNode> = [
351
- ...pathParams.map((p) => {
352
- const type = resolveParamsType({ node, param: p, resolver })
353
- return createFunctionParameter({
354
- name: p.name,
355
- type: wrapTypeNode(type),
356
- optional: !p.required,
357
- })
358
- }),
359
- ...(bodyType
360
- ? [
361
- createFunctionParameter({
362
- name: dataName,
363
- type: bodyType,
364
- optional: !bodyRequired,
365
- }),
366
- ]
367
- : []),
368
- ...buildGroupParam({
369
- name: paramsName,
370
- node,
371
- params: queryParams,
372
- groupType: queryGroupType,
373
- resolver,
374
- wrapType,
375
- }),
376
- ...buildGroupParam({
377
- name: headersName,
378
- node,
379
- params: headerParams,
380
- groupType: headerGroupType,
381
- resolver,
382
- wrapType,
383
- }),
384
- ]
355
+ const params: Array<FunctionParameterNode> = []
385
356
 
357
+ if (paramsType === 'object') {
358
+ const children = [...pathParams.map(toProperty), ...bodyProperty, ...trailingGroups.flatMap(buildGroupProperty)]
386
359
  if (children.length) {
387
- params.push(
388
- createParameterGroup({
389
- properties: children,
390
- default: children.every((c) => c.optional) ? '{}' : undefined,
391
- }),
392
- )
360
+ params.push(createFunctionParameter({ properties: children, default: emptyObjectDefault(children) }))
393
361
  }
394
362
  } else {
395
- if (pathParams.length) {
396
- if (pathParamsType === 'inlineSpread') {
397
- const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]!)
398
- params.push(
399
- createFunctionParameter({
400
- name: pathName,
401
- type: spreadType ? wrapType(spreadType) : undefined,
402
- rest: true,
403
- }),
404
- )
405
- } else {
406
- const pathChildren = pathParams.map((p) => {
407
- const type = resolveParamsType({ node, param: p, resolver })
408
- return createFunctionParameter({
409
- name: p.name,
410
- type: wrapTypeNode(type),
411
- optional: !p.required,
412
- })
413
- })
414
- params.push(
415
- createParameterGroup({
416
- properties: pathChildren,
417
- inline: pathParamsType === 'inline',
418
- default: pathParamsDefault ?? (pathChildren.every((c) => c.optional) ? '{}' : undefined),
419
- }),
420
- )
421
- }
422
- }
423
-
424
- if (bodyType) {
425
- params.push(
426
- createFunctionParameter({
427
- name: dataName,
428
- type: bodyType,
429
- optional: !bodyRequired,
430
- }),
431
- )
363
+ if (pathParamsType === 'inlineSpread' && pathParams.length) {
364
+ const spreadType = resolver?.resolvePathParamsName(node, pathParams[0]!)
365
+ params.push(createFunctionParameter({ name: pathName, type: spreadType ? wrapType(spreadType) : undefined, rest: true }))
366
+ } else if (pathParamsType === 'inline') {
367
+ params.push(...pathParams.map((p) => createFunctionParameter(toProperty(p))))
368
+ } else if (pathParams.length) {
369
+ const pathChildren = pathParams.map(toProperty)
370
+ params.push(createFunctionParameter({ properties: pathChildren, default: pathParamsDefault ?? emptyObjectDefault(pathChildren) }))
432
371
  }
433
372
 
434
- params.push(
435
- ...buildGroupParam({
436
- name: paramsName,
437
- node,
438
- params: queryParams,
439
- groupType: queryGroupType,
440
- resolver,
441
- wrapType,
442
- }),
443
- )
444
- params.push(
445
- ...buildGroupParam({
446
- name: headersName,
447
- node,
448
- params: headerParams,
449
- groupType: headerGroupType,
450
- resolver,
451
- wrapType,
452
- }),
453
- )
373
+ params.push(...bodyProperty.map((p) => createFunctionParameter(p)))
374
+ params.push(...trailingGroups.flatMap(buildGroupParam))
454
375
  }
455
376
 
456
377
  params.push(...extraParams)
@@ -459,80 +380,53 @@ export function createOperationParams(node: OperationNode, options: CreateOperat
459
380
  }
460
381
 
461
382
  /**
462
- * Builds a single {@link FunctionParameterNode} for a query or header group.
463
- * Returns an empty array when there are no params to emit.
464
- *
465
- * If a pre-resolved `groupType` is provided it emits `name: GroupType`.
466
- * Otherwise, it builds an inline struct from the individual params.
383
+ * Shared arguments for building a query or header parameter group.
467
384
  */
468
- function buildGroupParam({
469
- name,
470
- node,
471
- params,
472
- groupType,
473
- resolver,
474
- wrapType,
475
- }: {
385
+ export type BuildGroupArgs = {
476
386
  name: string
477
387
  node: OperationNode
478
388
  params: Array<ParameterNode>
479
- groupType: ParamGroupType | null | undefined
389
+ groupType: ParamGroupType | null
480
390
  resolver: OperationParamsResolver | undefined
481
- wrapType: (type: string) => ParamsTypeNode
482
- }): Array<FunctionParameterNode> {
391
+ wrapType: (type: string) => string
392
+ }
393
+
394
+ /**
395
+ * Builds the property descriptor for a query or header group.
396
+ * Returns an empty array when there are no params to emit.
397
+ *
398
+ * A pre-resolved `groupType` emits `name: GroupType`. Otherwise it builds an inline
399
+ * {@link TypeLiteralNode} from the individual params.
400
+ */
401
+ function buildGroupProperty({ name, node, params, groupType, resolver, wrapType }: BuildGroupArgs): Array<GroupProperty> {
483
402
  if (groupType) {
484
- const type = groupType.type.kind === 'ParamsType' && groupType.type.variant === 'reference' ? wrapType(groupType.type.name) : groupType.type
485
- return [createFunctionParameter({ name, type, optional: groupType.optional })]
403
+ const type = typeof groupType.type === 'string' ? wrapType(groupType.type) : groupType.type
404
+ return [{ name, type, optional: groupType.optional }]
486
405
  }
487
406
  if (params.length) {
488
- return [
489
- createFunctionParameter({
490
- name,
491
- type: toStructType({ node, params, resolver }),
492
- optional: params.every((p) => !p.required),
493
- }),
494
- ]
407
+ return [{ name, type: buildTypeLiteral({ node, params, resolver }), optional: params.every((p) => !p.required) }]
495
408
  }
496
409
  return []
497
410
  }
498
411
 
499
412
  /**
500
- * Derives a {@link ParamGroupType} from the resolver's group method.
501
- * Returns `null` when the group name equals the individual param name (no real group).
413
+ * Builds a single {@link FunctionParameterNode} for a query or header group.
414
+ * Returns an empty array when there are no params to emit.
415
+ *
416
+ * A pre-resolved `groupType` emits `name: GroupType`. Otherwise it builds an inline
417
+ * {@link TypeLiteralNode} from the individual params.
502
418
  */
503
- function resolveGroupType({
504
- node,
505
- params,
506
- groupMethod,
507
- resolver,
508
- }: {
509
- node: OperationNode
510
- params: Array<ParameterNode>
511
- groupMethod: (_node: OperationNode, _param: ParameterNode) => string
512
- resolver: OperationParamsResolver
513
- }): ParamGroupType | null {
514
- if (!params.length) {
515
- return null
516
- }
517
- const firstParam = params[0]!
518
- const groupName = groupMethod.call(resolver, node, firstParam)
519
- if (groupName === resolver.resolveParamName(node, firstParam)) {
520
- return null
521
- }
522
- const allOptional = params.every((p) => !p.required)
523
- return {
524
- type: createParamsType({ variant: 'reference', name: groupName }),
525
- optional: allOptional,
526
- }
419
+ export function buildGroupParam(args: BuildGroupArgs): Array<FunctionParameterNode> {
420
+ return buildGroupProperty(args).map((p) => createFunctionParameter(p))
527
421
  }
528
422
 
529
423
  /**
530
- * Builds a {@link TypeNode} with `variant: 'struct'` for an inline anonymous type grouping named fields.
424
+ * Builds a {@link TypeLiteralNode} for an inline anonymous type grouping named fields.
531
425
  *
532
426
  * Used when query or header parameters have no dedicated group type name.
533
427
  * Each language printer renders this appropriately (TypeScript: `{ petId: string; name?: string }`).
534
428
  */
535
- function toStructType({
429
+ export function buildTypeLiteral({
536
430
  node,
537
431
  params,
538
432
  resolver,
@@ -540,13 +434,12 @@ function toStructType({
540
434
  node: OperationNode
541
435
  params: Array<ParameterNode>
542
436
  resolver: OperationParamsResolver | undefined
543
- }): ParamsTypeNode {
544
- return createParamsType({
545
- variant: 'struct',
546
- properties: params.map((p) => ({
437
+ }): TypeLiteralNode {
438
+ return createTypeLiteral({
439
+ members: params.map((p) => ({
547
440
  name: p.name,
441
+ type: resolveParamType({ node, param: p, resolver }),
548
442
  optional: !p.required,
549
- type: resolveParamsType({ node, param: p, resolver }),
550
443
  })),
551
444
  })
552
445
  }
@@ -728,39 +621,6 @@ export function combineImports(imports: Array<ImportNode>, exports: Array<Export
728
621
  return result
729
622
  }
730
623
 
731
- /**
732
- * Extracts all string content from a `CodeNode` tree recursively.
733
- *
734
- * Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
735
- * and nested node content. Used internally to build the full source string for import filtering.
736
- */
737
- export function extractStringsFromNodes(nodes: Array<CodeNode> | undefined): string {
738
- if (!nodes?.length) return ''
739
- return nodes
740
- .map((node) => {
741
- // Backward-compat: compiled plugins may still pass bare strings at runtime
742
- if (typeof node === 'string') return node as string
743
- if (node.kind === 'Text') return node.value
744
- if (node.kind === 'Break') return ''
745
- if (node.kind === 'Jsx') return node.value
746
-
747
- const parts: Array<string> = []
748
-
749
- if ('params' in node && node.params) parts.push(node.params)
750
- if ('generics' in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(', ') : node.generics)
751
- if ('returnType' in node && node.returnType) parts.push(node.returnType)
752
- if ('type' in node && typeof node.type === 'string') parts.push(node.type)
753
-
754
- const nested = extractStringsFromNodes(node.nodes)
755
-
756
- if (nested) parts.push(nested)
757
-
758
- return parts.join('\n')
759
- })
760
- .filter(Boolean)
761
- .join('\n')
762
- }
763
-
764
624
  /**
765
625
  * Resolves the schema name of a ref node, falling back through `ref` → `name` → nested `schema.name`.
766
626
  *
@@ -0,0 +1,34 @@
1
+ import type { CodeNode } from '../nodes/code.ts'
2
+
3
+ /**
4
+ * Extracts all string content from a `CodeNode` tree recursively.
5
+ *
6
+ * Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
7
+ * and nested node content. Used to build the full source string for import filtering.
8
+ */
9
+ export function extractStringsFromNodes(nodes: Array<CodeNode> | undefined): string {
10
+ if (!nodes?.length) return ''
11
+ return nodes
12
+ .map((node) => {
13
+ // Backward-compat: compiled plugins may still pass bare strings at runtime
14
+ if (typeof node === 'string') return node as string
15
+ if (node.kind === 'Text') return node.value
16
+ if (node.kind === 'Break') return ''
17
+ if (node.kind === 'Jsx') return node.value
18
+
19
+ const parts: Array<string> = []
20
+
21
+ if ('params' in node && node.params) parts.push(node.params)
22
+ if ('generics' in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(', ') : node.generics)
23
+ if ('returnType' in node && node.returnType) parts.push(node.returnType)
24
+ if ('type' in node && typeof node.type === 'string') parts.push(node.type)
25
+
26
+ const nested = extractStringsFromNodes(node.nodes)
27
+
28
+ if (nested) parts.push(nested)
29
+
30
+ return parts.join('\n')
31
+ })
32
+ .filter(Boolean)
33
+ .join('\n')
34
+ }
@@ -1,7 +1,22 @@
1
1
  import { isIdentifier, pascalCase, singleQuote } from '@internals/utils'
2
2
  import { INDENT } from '../constants.ts'
3
+ import type { OperationNode, ParameterNode } from '../nodes/index.ts'
4
+ import type { OperationParamsResolver, ParamGroupType } from './ast.ts'
3
5
 
4
6
  export { isValidVarName } from '@internals/utils'
7
+ export { extractStringsFromNodes } from './extractStringsFromNodes.ts'
8
+ export {
9
+ buildGroupParam,
10
+ buildTypeLiteral,
11
+ caseParams,
12
+ collectUsedSchemaNames,
13
+ containsCircularRef,
14
+ findCircularSchemas,
15
+ isStringType,
16
+ resolveParamType,
17
+ syncSchemaRef,
18
+ } from './ast.ts'
19
+ export type { BuildGroupArgs, ParamGroupType } from './ast.ts'
5
20
 
6
21
  /**
7
22
  * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
@@ -295,3 +310,32 @@ export function findDiscriminator(mapping: Record<string, string> | undefined, r
295
310
  if (!mapping || !ref) return null
296
311
  return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null
297
312
  }
313
+
314
+ /**
315
+ * Derives a {@link ParamGroupType} for a query or header group from the resolver.
316
+ *
317
+ * Returns `null` when there is no resolver, no params, or the group name equals the
318
+ * individual param name (so there is no real group to emit).
319
+ */
320
+ export function resolveGroupType({
321
+ node,
322
+ params,
323
+ group,
324
+ resolver,
325
+ }: {
326
+ node: OperationNode
327
+ params: Array<ParameterNode>
328
+ group: 'query' | 'header'
329
+ resolver: OperationParamsResolver | undefined
330
+ }): ParamGroupType | null {
331
+ if (!resolver || !params.length) {
332
+ return null
333
+ }
334
+ const firstParam = params[0]!
335
+ const groupMethod = group === 'query' ? resolver.resolveQueryParamsName : resolver.resolveHeaderParamsName
336
+ const groupName = groupMethod.call(resolver, node, firstParam)
337
+ if (groupName === resolver.resolveParamName(node, firstParam)) {
338
+ return null
339
+ }
340
+ return { type: groupName, optional: params.every((p) => !p.required) }
341
+ }
package/src/visitor.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import type { VisitorDepth } from './constants.ts'
2
2
  import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
3
- import { createParameter, createProperty } from './factory.ts'
3
+ import { nodeRebuilders, VISITOR_KEY_BY_KIND, VISITOR_KEYS } from './registry.ts'
4
4
  import type {
5
5
  ContentNode,
6
6
  InputNode,
7
7
  Node,
8
- NodeKind,
9
8
  OperationNode,
10
9
  OutputNode,
11
10
  ParameterNode,
@@ -281,24 +280,6 @@ export type CollectOptions<T> = CollectVisitor<T> & {
281
280
  parent?: Node
282
281
  }
283
282
 
284
- /**
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
283
  const visitorKeysByKind = VISITOR_KEYS as Record<string, ReadonlyArray<string> | undefined>
303
284
 
304
285
  /**
@@ -336,21 +317,6 @@ function* getChildren(node: Node, recurse: boolean): Generator<Node, void, undef
336
317
  }
337
318
  }
338
319
 
339
- /**
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
320
  /**
355
321
  * Invokes the visitor callback that matches `node.kind`, passing the traversal
356
322
  * context. Returns the callback's result (a replacement node, a collected
@@ -455,18 +421,8 @@ export function transform(node: Node, options: TransformOptions): Node {
455
421
  // changed" by identity and ancestors can avoid reallocating.
456
422
  if (rebuilt === node) return node
457
423
 
458
- const finalize = nodeFinalizers[rebuilt.kind]
459
- return finalize ? finalize(rebuilt) : rebuilt
460
- }
461
-
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),
424
+ const rebuild = nodeRebuilders[rebuilt.kind]
425
+ return rebuild ? rebuild(rebuilt) : rebuilt
470
426
  }
471
427
 
472
428
  /**
@@ -1,8 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __name = (target, value) => __defProp(target, "name", {
4
- value,
5
- configurable: true
6
- });
7
- //#endregion
8
- export { __name as t };