@kubb/ast 5.0.0-beta.60 → 5.0.0-beta.62

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 (87) hide show
  1. package/README.md +2 -1
  2. package/dist/defineMacro-BryDiuQl.cjs +115 -0
  3. package/dist/defineMacro-BryDiuQl.cjs.map +1 -0
  4. package/dist/defineMacro-CNkUpxia.js +99 -0
  5. package/dist/defineMacro-CNkUpxia.js.map +1 -0
  6. package/dist/defineMacro-Vw-xQvq7.d.ts +467 -0
  7. package/dist/factory.cjs +64 -4
  8. package/dist/factory.cjs.map +1 -0
  9. package/dist/factory.d.ts +2 -37
  10. package/dist/factory.js +58 -3
  11. package/dist/factory.js.map +1 -0
  12. package/dist/{index-BzjwdK2M.d.ts → index-CTaN_ee_.d.ts} +235 -198
  13. package/dist/index.cjs +33 -32
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +36 -36
  16. package/dist/index.js +19 -14
  17. package/dist/index.js.map +1 -1
  18. package/dist/macros.cjs +130 -0
  19. package/dist/macros.cjs.map +1 -0
  20. package/dist/macros.d.ts +61 -0
  21. package/dist/macros.js +128 -0
  22. package/dist/macros.js.map +1 -0
  23. package/dist/{operationParams-BZ07xDm0.d.ts → operationParams-B44p6EQy.d.ts} +2 -2
  24. package/dist/{response-DS5S3IG4.cjs → response-BeG6hq2D.cjs} +389 -173
  25. package/dist/response-BeG6hq2D.cjs.map +1 -0
  26. package/dist/{response-DKxTr522.js → response-Rd1uisM1.js} +377 -162
  27. package/dist/response-Rd1uisM1.js.map +1 -0
  28. package/dist/types-DQTgVSwE.d.ts +314 -0
  29. package/dist/types.d.ts +5 -5
  30. package/dist/{utils-D83JA6Xx.cjs → utils-BDI3kXlb.cjs} +20 -753
  31. package/dist/utils-BDI3kXlb.cjs.map +1 -0
  32. package/dist/{utils-Dj_KoXMv.js → utils-DW9rnfsD.js} +11 -630
  33. package/dist/utils-DW9rnfsD.js.map +1 -0
  34. package/dist/utils.cjs +11 -9
  35. package/dist/utils.d.ts +38 -4
  36. package/dist/utils.js +4 -3
  37. package/dist/visitor-B7Zlnp4c.cjs +604 -0
  38. package/dist/visitor-B7Zlnp4c.cjs.map +1 -0
  39. package/dist/visitor-DepQEKyp.js +510 -0
  40. package/dist/visitor-DepQEKyp.js.map +1 -0
  41. package/package.json +5 -1
  42. package/src/constants.ts +0 -30
  43. package/src/dedupe.ts +1 -9
  44. package/src/defineMacro.ts +139 -0
  45. package/src/{node.ts → defineNode.ts} +19 -42
  46. package/src/dialect.ts +42 -19
  47. package/src/factory.ts +3 -90
  48. package/src/index.ts +9 -15
  49. package/src/macros/index.ts +3 -0
  50. package/src/macros/macroDiscriminatorEnum.ts +50 -0
  51. package/src/macros/macroEnumName.ts +33 -0
  52. package/src/macros/macroSimplifyUnion.ts +60 -0
  53. package/src/nodes/code.ts +32 -75
  54. package/src/nodes/content.ts +2 -7
  55. package/src/nodes/file.ts +102 -14
  56. package/src/nodes/function.ts +46 -46
  57. package/src/nodes/index.ts +2 -3
  58. package/src/nodes/input.ts +13 -9
  59. package/src/nodes/operation.ts +5 -5
  60. package/src/nodes/output.ts +1 -1
  61. package/src/nodes/parameter.ts +6 -6
  62. package/src/nodes/property.ts +6 -6
  63. package/src/nodes/requestBody.ts +6 -13
  64. package/src/nodes/response.ts +93 -8
  65. package/src/nodes/schema.ts +1 -1
  66. package/src/registry.ts +1 -1
  67. package/src/types.ts +4 -4
  68. package/src/utils/extractStringsFromNodes.ts +1 -0
  69. package/src/utils/index.ts +2 -2
  70. package/src/utils/operationParams.ts +1 -1
  71. package/src/utils/refs.ts +27 -1
  72. package/src/utils/schemaMerge.ts +34 -0
  73. package/src/visitor.ts +2 -13
  74. package/dist/extractStringsFromNodes-WMYJ8nQL.d.ts +0 -82
  75. package/dist/factory-Cl8Z7mcc.cjs +0 -299
  76. package/dist/factory-Cl8Z7mcc.cjs.map +0 -1
  77. package/dist/factory-Du7nEP4B.js +0 -282
  78. package/dist/factory-Du7nEP4B.js.map +0 -1
  79. package/dist/response-DKxTr522.js.map +0 -1
  80. package/dist/response-DS5S3IG4.cjs.map +0 -1
  81. package/dist/types-olVl9v5p.d.ts +0 -764
  82. package/dist/utils-D83JA6Xx.cjs.map +0 -1
  83. package/dist/utils-Dj_KoXMv.js.map +0 -1
  84. package/src/mocks.ts +0 -56
  85. package/src/nodes/http.ts +0 -85
  86. package/src/transformers.ts +0 -191
  87. /package/src/{printer.ts → definePrinter.ts} +0 -0
@@ -0,0 +1,139 @@
1
+ import type { VisitorDepth } from './constants.ts'
2
+ import type { VisitorKey } from './defineNode.ts'
3
+ import { visitorKeys } from './defineNode.ts'
4
+ import type { Node } from './nodes/index.ts'
5
+ import type { Visitor, VisitorContext } from './visitor.ts'
6
+ import { transform } from './visitor.ts'
7
+
8
+ /**
9
+ * Ordering hint shared by macros and plugins. `pre` runs before unmarked items, `post` after,
10
+ * and `undefined` keeps declaration order.
11
+ */
12
+ export type Enforce = 'pre' | 'post'
13
+
14
+ /**
15
+ * Sort weight for an `enforce` hint. `pre` sorts before unmarked items and `post` after, so a plain
16
+ * list keeps its authored order.
17
+ */
18
+ function enforceWeight(enforce?: Enforce): number {
19
+ if (enforce === 'pre') return 0
20
+ if (enforce === 'post') return 2
21
+ return 1
22
+ }
23
+
24
+ /**
25
+ * A named, composable transform over the Kubb AST. It carries the same per-kind callbacks as a
26
+ * {@link Visitor} (`schema`, `operation`, …), plus a `name`, an optional `enforce` order, and an
27
+ * optional `when` gate. Macros run on the shared AST, so the same macro works across every adapter
28
+ * and output target. Exports follow the `macro<Name>` convention, mirroring plugins (`pluginTs`).
29
+ */
30
+ export type Macro = Visitor & {
31
+ /**
32
+ * Macro identifier, surfaced in diagnostics. Follows the `macro<Name>` convention.
33
+ */
34
+ name: string
35
+ /**
36
+ * Ordering hint. `pre` macros run before unmarked macros, `post` macros run after.
37
+ * Ordering within a bucket follows list order.
38
+ */
39
+ enforce?: Enforce
40
+ /**
41
+ * Gate checked against the current node before any callback runs. When it returns `false`
42
+ * the macro is skipped for that node.
43
+ */
44
+ when?: (node: Node) => boolean
45
+ }
46
+
47
+ /**
48
+ * Types a macro for inference and a single construction site, mirroring `definePlugin`.
49
+ * Adds no runtime behavior.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * const macroUntagged = defineMacro({
54
+ * name: 'untagged',
55
+ * operation(node) {
56
+ * return node.tags?.length ? undefined : { ...node, tags: ['untagged'] }
57
+ * },
58
+ * })
59
+ * ```
60
+ */
61
+ export function defineMacro(macro: Macro): Macro {
62
+ return macro
63
+ }
64
+
65
+ type MacroCallback = (node: Node, context: VisitorContext) => Node | null | undefined
66
+
67
+ type ChainProps = {
68
+ macros: ReadonlyArray<Macro>
69
+ key: VisitorKey
70
+ node: Node
71
+ context: VisitorContext
72
+ }
73
+
74
+ /**
75
+ * Runs every macro's callback for one node kind in order, chaining the result so each macro sees
76
+ * the previous macro's output. Returns `undefined` when nothing changed, so `transform` keeps the
77
+ * original reference (structural sharing).
78
+ */
79
+ function chain({ macros, key, node, context }: ChainProps): Node | undefined {
80
+ let current = node
81
+
82
+ for (const macro of macros) {
83
+ const callback = macro[key] as MacroCallback | undefined
84
+ if (!callback) continue
85
+ if (macro.when && !macro.when(current)) continue
86
+
87
+ const next = callback(current, context)
88
+ if (next != null) current = next
89
+ }
90
+
91
+ return current === node ? undefined : current
92
+ }
93
+
94
+ /**
95
+ * Folds an ordered list of macros into a single {@link Visitor} that `transform` (and the per-plugin
96
+ * transform layer in `@kubb/core`) can run. Macros are stable-sorted by `enforce`, then applied
97
+ * sequentially per node so later macros see earlier output. This differs from a plain visitor, which
98
+ * has no names, ordering, or composition.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * const visitor = composeMacros([macroSimplifyUnion, macroDiscriminatorEnum])
103
+ * const next = transform(root, visitor)
104
+ * ```
105
+ */
106
+ export function composeMacros(macros: ReadonlyArray<Macro>): Visitor {
107
+ const ordered = [...macros].sort((a, b) => enforceWeight(a.enforce) - enforceWeight(b.enforce))
108
+
109
+ const visitor: Visitor = {}
110
+ for (const key of visitorKeys) {
111
+ if (!ordered.some((macro) => typeof macro[key] === 'function')) continue
112
+
113
+ const callback = (node: Node, context: VisitorContext) => chain({ macros: ordered, key, node, context })
114
+ ;(visitor as Record<VisitorKey, MacroCallback>)[key] = callback
115
+ }
116
+
117
+ return visitor
118
+ }
119
+
120
+ /**
121
+ * Runs a list of macros over a node tree and returns the rewritten tree. Keeps `transform`'s
122
+ * structural sharing, so an empty or no-op macro list returns the same reference. Pass
123
+ * `depth: 'shallow'` to rewrite the root node only.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const next = applyMacros(root, [macroIntegerToString])
128
+ * ```
129
+ *
130
+ * @example Apply to the root node only
131
+ * ```ts
132
+ * const named = applyMacros(node, [macroEnumName({ parentName, propName, enumSuffix })], { depth: 'shallow' })
133
+ * ```
134
+ */
135
+ export function applyMacros<TNode extends Node>(root: TNode, macros: ReadonlyArray<Macro>, options?: { depth?: VisitorDepth }): TNode {
136
+ if (macros.length === 0) return root
137
+
138
+ return transform(root, { ...composeMacros(macros), ...options }) as TNode
139
+ }
@@ -1,11 +1,16 @@
1
+ import type { Dialect } from './dialect.ts'
1
2
  import type { BaseNode, NodeKind } from './nodes/base.ts'
2
- import type { SchemaNode } from './nodes/index.ts'
3
3
 
4
4
  /**
5
- * Visitor callback names, one per traversable node kind. Kept in sync with the
6
- * keys of `Visitor` in `visitor.ts`.
5
+ * Visitor callback names, one per traversable node kind, in traversal order.
6
+ * Kept in sync with the keys of `Visitor` in `visitor.ts`.
7
7
  */
8
- type VisitorKey = 'input' | 'output' | 'operation' | 'schema' | 'property' | 'parameter' | 'response'
8
+ export const visitorKeys = ['input', 'output', 'operation', 'schema', 'property', 'parameter', 'response'] as const
9
+
10
+ /**
11
+ * One of the {@link visitorKeys} callback names.
12
+ */
13
+ export type VisitorKey = (typeof visitorKeys)[number]
9
14
 
10
15
  /**
11
16
  * Distributive `Omit` that preserves each member of a union.
@@ -27,25 +32,6 @@ function isKind<T extends BaseNode>(kind: NodeKind) {
27
32
  return (node: unknown): node is T => (node as BaseNode).kind === kind
28
33
  }
29
34
 
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
35
  /**
50
36
  * The single definition derived from one {@link defineNode} call: the node's
51
37
  * `create` builder, its `is` guard, and the traversal metadata the registry
@@ -57,9 +43,11 @@ export type NodeDef<TNode extends BaseNode = BaseNode, TInput = never> = {
57
43
  */
58
44
  kind: NodeKind
59
45
  /**
60
- * Builds a node from its input, applying `defaults` and the optional `build` hook.
46
+ * Builds a node from its input, applying `defaults` and the optional `build` hook. An
47
+ * optional `dialect` is forwarded to `build` so nodes can derive spec-specific fields
48
+ * (e.g. a schema's `optional`/`nullish`) through `dialect.schema.optionality` when one is given.
61
49
  */
62
- create: (input: TInput) => TNode
50
+ create: (input: TInput, dialect?: Dialect) => TNode
63
51
  /**
64
52
  * Type guard matching this node kind.
65
53
  */
@@ -72,20 +60,14 @@ export type NodeDef<TNode extends BaseNode = BaseNode, TInput = never> = {
72
60
  * Visitor callback name. Feeds `VISITOR_KEY_BY_KIND`.
73
61
  */
74
62
  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
63
  }
81
64
 
82
65
  type DefineNodeConfig<TNode extends BaseNode, TInput, TBuilt extends object> = {
83
66
  kind: TNode['kind']
84
67
  defaults?: Partial<TNode>
85
- build?: (input: TInput) => TBuilt
68
+ build?: (input: TInput, dialect?: Dialect) => TBuilt
86
69
  children?: ReadonlyArray<string>
87
70
  visitorKey?: VisitorKey
88
- rebuild?: boolean
89
71
  }
90
72
 
91
73
  /**
@@ -93,36 +75,31 @@ type DefineNodeConfig<TNode extends BaseNode, TInput, TBuilt extends object> = {
93
75
  * metadata. `create` merges `defaults`, the `build` hook (or the raw input), and the
94
76
  * `kind`, so node construction lives in one place without scattered `as` casts.
95
77
  *
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
78
  * @example Simple node
101
79
  * ```ts
102
80
  * const importDef = defineNode<ImportNode>({ kind: 'Import' })
103
81
  * const createImport = importDef.create
104
82
  * ```
105
83
  *
106
- * @example Node with a build hook that is rerun on transform
84
+ * @example Node with a build hook
107
85
  * ```ts
108
86
  * const propertyDef = defineNode<PropertyNode, UserPropertyNode>({
109
87
  * kind: 'Property',
110
88
  * build: (props) => ({ ...props, required: props.required ?? false }),
111
89
  * children: ['schema'],
112
90
  * visitorKey: 'property',
113
- * rebuild: true,
114
91
  * })
115
92
  * ```
116
93
  */
117
94
  export function defineNode<TNode extends BaseNode, TInput = Omit<TNode, 'kind'>, TBuilt extends object = Omit<TNode, 'kind'>>(
118
95
  config: DefineNodeConfig<TNode, TInput, TBuilt>,
119
96
  ): NodeDef<TNode, TInput> {
120
- const { kind, defaults, build, children, visitorKey, rebuild } = config
97
+ const { kind, defaults, build, children, visitorKey } = config
121
98
 
122
- function create(input: TInput): TNode {
123
- const base = build ? build(input) : input
99
+ function create(input: TInput, dialect?: Dialect): TNode {
100
+ const base = build ? build(input, dialect) : input
124
101
  return { ...defaults, ...(base as object), kind } as TNode
125
102
  }
126
103
 
127
- return { kind, create, is: isKind<TNode>(kind), children, visitorKey, rebuild }
104
+ return { kind, create, is: isKind<TNode>(kind), children, visitorKey }
128
105
  }
package/src/dialect.ts CHANGED
@@ -1,53 +1,76 @@
1
+ import type { SchemaNode } from './nodes/index.ts'
2
+
1
3
  /**
2
4
  * The spec-specific questions a schema parser answers while turning a source document into Kubb
3
5
  * AST nodes. The rest of the pipeline is generic JSON Schema, so this is the one seam where
4
6
  * OpenAPI, AsyncAPI, and plain JSON Schema differ.
5
7
  */
6
8
  export type SchemaDialect<TSchema = unknown, TRef = TSchema, TDiscriminated = TSchema, TDocument = unknown> = {
7
- /**
8
- * Identifies the dialect in logs and diagnostics.
9
- */
10
- name: string
11
9
  /**
12
10
  * Whether the schema is nullable.
13
11
  */
14
- isNullable: (schema?: TSchema) => boolean
12
+ isNullable(schema?: TSchema): boolean
15
13
  /**
16
14
  * Whether the value is a `$ref` pointer.
17
15
  */
18
- isReference: (value?: unknown) => value is TRef
16
+ isReference(value?: unknown): value is TRef
19
17
  /**
20
18
  * Whether the schema carries a discriminator for polymorphism.
21
19
  */
22
- isDiscriminator: (value?: unknown) => value is TDiscriminated
20
+ isDiscriminator(value?: unknown): value is TDiscriminated
23
21
  /**
24
22
  * Whether the schema is binary data, converted to a `blob` node.
25
23
  */
26
- isBinary: (schema: TSchema) => boolean
24
+ isBinary(schema: TSchema): boolean
27
25
  /**
28
26
  * Resolves a local `$ref` against the document, or nullish when it cannot.
29
27
  */
30
- resolveRef: <TResolved>(document: TDocument, ref: string) => TResolved | null | undefined
28
+ resolveRef<TResolved>(document: TDocument, ref: string): TResolved | null | undefined
29
+ /**
30
+ * Derives a schema's `optional`/`nullish` flags from a parent's `required` value and the
31
+ * schema's own `nullable`. How "required" and "nullable" combine is spec-specific, so the
32
+ * dialect owns it. Method syntax keeps a concrete dialect assignable to the base
33
+ * `SchemaDialect` (bivariant parameters).
34
+ */
35
+ optionality(schema: SchemaNode, required: boolean): SchemaNode
36
+ }
37
+
38
+ /**
39
+ * A spec adapter's dialect. `name` identifies it in logs and diagnostics; `schema` holds the
40
+ * spec-specific schema questions the parser answers.
41
+ */
42
+ export type Dialect<TSchema = unknown, TRef = TSchema, TDiscriminated = TSchema, TDocument = unknown> = {
43
+ /**
44
+ * Identifies the dialect in logs and diagnostics.
45
+ */
46
+ name: string
47
+ /**
48
+ * The spec-specific schema behavior. See {@link SchemaDialect}.
49
+ */
50
+ schema: SchemaDialect<TSchema, TRef, TDiscriminated, TDocument>
31
51
  }
32
52
 
33
53
  /**
34
- * Types a {@link SchemaDialect} for an adapter. Adds no runtime behavior and only pins the
54
+ * Types a {@link Dialect} for an adapter. Adds no runtime behavior and only pins the
35
55
  * dialect's type for inference.
36
56
  *
37
57
  * @example
38
58
  * ```ts
39
- * export const oasDialect = defineSchemaDialect({
59
+ * export const oasDialect = defineDialect({
40
60
  * name: 'oas',
41
- * isNullable,
42
- * isReference,
43
- * isDiscriminator,
44
- * isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
45
- * resolveRef,
61
+ * schema: {
62
+ * isNullable,
63
+ * isReference,
64
+ * isDiscriminator,
65
+ * isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
66
+ * resolveRef,
67
+ * optionality,
68
+ * },
46
69
  * })
47
70
  * ```
48
71
  */
49
- export function defineSchemaDialect<TSchema, TRef, TDiscriminated, TDocument>(
50
- dialect: SchemaDialect<TSchema, TRef, TDiscriminated, TDocument>,
51
- ): SchemaDialect<TSchema, TRef, TDiscriminated, TDocument> {
72
+ export function defineDialect<TSchema, TRef, TDiscriminated, TDocument>(
73
+ dialect: Dialect<TSchema, TRef, TDiscriminated, TDocument>,
74
+ ): Dialect<TSchema, TRef, TDiscriminated, TDocument> {
52
75
  return dialect
53
76
  }
package/src/factory.ts CHANGED
@@ -1,16 +1,12 @@
1
- import { hash } from 'node:crypto'
2
- import path from 'node:path'
3
- import { trimExtName } from '@internals/utils'
4
- import type { FileNode, Node } from './nodes/index.ts'
5
- import { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
6
- import { combineExports, combineImports, combineSources } from './utils/fileMerge.ts'
1
+ import type { Node } from './nodes/index.ts'
7
2
 
8
3
  // Node constructors, grouped under the `factory` namespace the way the TypeScript compiler exposes
9
4
  // `ts.factory.createX`. Aggregating them here lets `export * as factory from './factory.ts'` in the
10
5
  // barrel surface every `createX` alongside the `createFile`/`update` helpers from a single module.
11
6
  export { createArrowFunction, createBreak, createConst, createFunction, createJsx, createText, createType } from './nodes/code.ts'
12
7
  export { createContent } from './nodes/content.ts'
13
- export { createExport, createImport, createSource } from './nodes/file.ts'
8
+ export { createExport, createFile, createImport, createSource } from './nodes/file.ts'
9
+ export type { UserFileNode } from './nodes/file.ts'
14
10
  export { createFunctionParameter, createFunctionParameters, createIndexedAccessType, createObjectBindingPattern, createTypeLiteral } from './nodes/function.ts'
15
11
  export { createInput } from './nodes/input.ts'
16
12
  export { createOperation } from './nodes/operation.ts'
@@ -46,86 +42,3 @@ export function update<T extends Node>(node: T, changes: Partial<T>): T {
46
42
 
47
43
  return node
48
44
  }
49
-
50
- /**
51
- * Input descriptor for {@link createFile}, before `id`, `name`, and `extname` are computed
52
- * and `imports`/`exports`/`sources` are deduplicated.
53
- */
54
- export type UserFileNode<TMeta extends object = object> = Omit<FileNode<TMeta>, 'kind' | 'id' | 'name' | 'extname' | 'imports' | 'exports' | 'sources'> &
55
- Pick<Partial<FileNode<TMeta>>, 'imports' | 'exports' | 'sources'>
56
-
57
- /**
58
- * Creates a fully resolved `FileNode` from a file input descriptor.
59
- *
60
- * Computes:
61
- * - `id` SHA256 hash of the file path
62
- * - `name` `baseName` without extension
63
- * - `extname` extension extracted from `baseName`
64
- *
65
- * Deduplicates:
66
- * - `sources` via `combineSources`
67
- * - `exports` via `combineExports`
68
- * - `imports` via `combineImports` (also filters unused imports)
69
- *
70
- * @throws {Error} when `baseName` has no extension.
71
- *
72
- * @example
73
- * ```ts
74
- * const file = createFile({
75
- * baseName: 'petStore.ts',
76
- * path: 'src/models/petStore.ts',
77
- * sources: [createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')] })],
78
- * imports: [createImport({ name: ['z'], path: 'zod' })],
79
- * exports: [createExport({ name: ['Pet'], path: './petStore' })],
80
- * })
81
- * // file.id = SHA256 hash of 'src/models/petStore.ts'
82
- * // file.name = 'petStore'
83
- * // file.extname = '.ts'
84
- * ```
85
- */
86
- export function createFile<TMeta extends object = object>(input: UserFileNode<TMeta>): FileNode<TMeta> {
87
- const rawExtname = path.extname(input.baseName)
88
- // Handle dotfile basename like '.ts' where path.extname returns ''
89
- const extname = (rawExtname || (input.baseName.startsWith('.') ? input.baseName : '')) as `.${string}`
90
- if (!extname) {
91
- throw new Error(`No extname found for ${input.baseName}`)
92
- }
93
-
94
- const source = (input.sources ?? [])
95
- .flatMap((item) => item.nodes ?? [])
96
- .map((node) => extractStringsFromNodes([node]))
97
- .filter(Boolean)
98
- .join('\n\n')
99
- const resolvedExports = input.exports?.length ? combineExports(input.exports) : []
100
- const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || undefined) : []
101
- const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name): name is string => Boolean(name)))
102
- const nameOf = (item: string | { propertyName: string; name?: string }): string => (typeof item === 'string' ? item : (item.name ?? item.propertyName))
103
- // Drop self-imports. Consolidating output (`mode: 'file'`) can place a symbol's
104
- // definition and a cross-file import of it in the same file. The first pass catches imports that
105
- // resolve to this file's own path. The second drops imports of names the file already defines,
106
- // the case consolidation produces when the import path no longer matches `input.path`. Sources
107
- // stay intact, so the local definition remains. Bare specifiers like `'zod'` never match a path.
108
- const resolvedImports = combinedImports
109
- .filter((imp) => imp.path !== input.path)
110
- .flatMap((imp) => {
111
- if (!Array.isArray(imp.name)) {
112
- return typeof imp.name === 'string' && localNames.has(imp.name) ? [] : [imp]
113
- }
114
- const kept = imp.name.filter((item) => !localNames.has(nameOf(item)))
115
- if (!kept.length) return []
116
- return [kept.length === imp.name.length ? imp : { ...imp, name: kept }]
117
- })
118
- const resolvedSources = input.sources?.length ? combineSources(input.sources) : []
119
-
120
- return {
121
- kind: 'File',
122
- ...input,
123
- id: hash('sha256', input.path, 'hex'),
124
- name: trimExtName(input.baseName),
125
- extname,
126
- imports: resolvedImports,
127
- exports: resolvedExports,
128
- sources: resolvedSources,
129
- meta: input.meta ?? ({} as TMeta),
130
- }
131
- }
package/src/index.ts CHANGED
@@ -1,19 +1,13 @@
1
- export { httpMethods, schemaTypes } from './constants.ts'
1
+ export { schemaTypes } from './constants.ts'
2
2
  export { applyDedupe, buildDedupePlan } from './dedupe.ts'
3
- export { defineSchemaDialect } from './dialect.ts'
4
- // Node constructors, also published as the `@kubb/ast/factory` subpath, mirroring `ts.factory.createX`.
5
- export * as factory from './factory.ts'
3
+ export { defineDialect } from './dialect.ts'
6
4
  export { isHttpOperationNode, narrowSchema } from './guards.ts'
7
- export { defineNode } from './node.ts'
8
- export type { NodeDef } from './node.ts'
9
- export { syncOptionality } from './node.ts'
10
- // Every node def plus `nodeDefs` in one line, so adding a node never edits this barrel. The visitor
11
- // tables derived from `nodeDefs` stay internal to `visitor.ts`.
12
- export * from './registry.ts'
13
- export { createPrinterFactory, definePrinter } from './printer.ts'
5
+ export { applyMacros, composeMacros, defineMacro } from './defineMacro.ts'
6
+ export { defineNode } from './defineNode.ts'
7
+ export { createPrinterFactory, definePrinter } from './definePrinter.ts'
14
8
  export { signatureOf } from './signature.ts'
15
- export { mergeAdjacentObjectsLazy, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
16
- export type * from './types.ts'
17
- // The node/AST helpers in ./utils/ live on the `@kubb/ast/utils` subpath, not the root barrel.
18
- export { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
19
9
  export { collect, transform, walk } from './visitor.ts'
10
+
11
+ export * as factory from './factory.ts'
12
+ export * from './registry.ts'
13
+ export type * from './types.ts'
@@ -0,0 +1,3 @@
1
+ export { macroDiscriminatorEnum } from './macroDiscriminatorEnum.ts'
2
+ export { macroEnumName } from './macroEnumName.ts'
3
+ export { macroSimplifyUnion } from './macroSimplifyUnion.ts'
@@ -0,0 +1,50 @@
1
+ import { defineMacro } from '../defineMacro.ts'
2
+ import { narrowSchema } from '../guards.ts'
3
+ import { createProperty } from '../nodes/property.ts'
4
+ import { createSchema } from '../nodes/schema.ts'
5
+
6
+ type Props = {
7
+ propertyName: string
8
+ values: Array<string>
9
+ enumName?: string
10
+ }
11
+
12
+ /**
13
+ * Builds a macro that replaces a discriminator property's schema with a string enum of the given
14
+ * values. Object schemas that lack the property are returned unchanged.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const macro = macroDiscriminatorEnum({ propertyName: 'type', values: ['dog', 'cat'] })
19
+ * const next = applyMacros(objectSchema, [macro], { depth: 'shallow' })
20
+ * ```
21
+ */
22
+ export function macroDiscriminatorEnum({ propertyName, values, enumName }: Props) {
23
+ return defineMacro({
24
+ name: 'discriminator-enum',
25
+ schema(node) {
26
+ const objectNode = narrowSchema(node, 'object')
27
+ if (!objectNode?.properties?.length) return undefined
28
+ if (!objectNode.properties.some((prop) => prop.name === propertyName)) return undefined
29
+
30
+ return createSchema({
31
+ ...objectNode,
32
+ properties: objectNode.properties.map((prop) => {
33
+ if (prop.name !== propertyName) return prop
34
+
35
+ return createProperty({
36
+ ...prop,
37
+ schema: createSchema({
38
+ type: 'enum',
39
+ primitive: 'string',
40
+ enumValues: values,
41
+ name: enumName,
42
+ readOnly: prop.schema.readOnly,
43
+ writeOnly: prop.schema.writeOnly,
44
+ }),
45
+ })
46
+ }),
47
+ })
48
+ },
49
+ })
50
+ }
@@ -0,0 +1,33 @@
1
+ import { defineMacro } from '../defineMacro.ts'
2
+ import { narrowSchema } from '../guards.ts'
3
+ import { enumPropName } from '../utils/refs.ts'
4
+
5
+ type Props = {
6
+ parentName: string | null | undefined
7
+ propName: string
8
+ enumSuffix: string
9
+ }
10
+
11
+ /**
12
+ * Builds a macro that names an inline enum schema from its parent and property name. Boolean enums
13
+ * are left anonymous; non-enum nodes are returned unchanged.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const macro = macroEnumName({ parentName: 'Pet', propName: 'status', enumSuffix: 'enum' })
18
+ * const named = applyMacros(propSchema, [macro], { depth: 'shallow' })
19
+ * ```
20
+ */
21
+ export function macroEnumName({ parentName, propName, enumSuffix }: Props) {
22
+ return defineMacro({
23
+ name: 'enum-name',
24
+ schema(node) {
25
+ const enumNode = narrowSchema(node, 'enum')
26
+
27
+ if (enumNode?.primitive === 'boolean') return { ...node, name: null }
28
+ if (enumNode) return { ...node, name: enumPropName(parentName, propName, enumSuffix) }
29
+
30
+ return undefined
31
+ },
32
+ })
33
+ }
@@ -0,0 +1,60 @@
1
+ import { defineMacro } from '../defineMacro.ts'
2
+ import { narrowSchema } from '../guards.ts'
3
+ import type { SchemaNode } from '../nodes/schema.ts'
4
+
5
+ type ScalarPrimitive = 'string' | 'number' | 'integer' | 'bigint' | 'boolean'
6
+
7
+ /**
8
+ * Scalar primitive schema types used for union simplification and type narrowing.
9
+ */
10
+ const SCALAR_PRIMITIVE_TYPES = new Set<ScalarPrimitive>(['string', 'number', 'integer', 'bigint', 'boolean'])
11
+
12
+ function isScalarPrimitive(type: string): type is ScalarPrimitive {
13
+ return SCALAR_PRIMITIVE_TYPES.has(type as ScalarPrimitive)
14
+ }
15
+
16
+ /**
17
+ * Filters union members, dropping enum members that a broader scalar primitive already covers.
18
+ */
19
+ function simplifyUnionMembers(members: Array<SchemaNode>): Array<SchemaNode> {
20
+ const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type))
21
+ if (!scalarPrimitives.size) return members
22
+
23
+ return members.filter((member) => {
24
+ const enumNode = narrowSchema(member, 'enum')
25
+ if (!enumNode) return true
26
+
27
+ const primitive = enumNode.primitive
28
+ if (!primitive) return true
29
+
30
+ const enumValueCount = enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0
31
+ if (enumValueCount <= 1) return true
32
+
33
+ if (scalarPrimitives.has(primitive)) return false
34
+ if ((primitive === 'integer' || primitive === 'number') && (scalarPrimitives.has('integer') || scalarPrimitives.has('number'))) return false
35
+
36
+ return true
37
+ })
38
+ }
39
+
40
+ /**
41
+ * Removes union members a broader scalar primitive already covers, such as a multi-value string enum
42
+ * sitting next to a plain `string`. Single-value enums are kept.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const next = applyMacros(unionSchema, [macroSimplifyUnion], { depth: 'shallow' })
47
+ * ```
48
+ */
49
+ export const macroSimplifyUnion = defineMacro({
50
+ name: 'simplify-union',
51
+ schema(node) {
52
+ const unionNode = narrowSchema(node, 'union')
53
+ if (!unionNode?.members?.length) return undefined
54
+
55
+ const simplified = simplifyUnionMembers(unionNode.members)
56
+ if (simplified.length === unionNode.members.length) return undefined
57
+
58
+ return { ...unionNode, members: simplified }
59
+ },
60
+ })