@kubb/ast 5.0.0-beta.61 → 5.0.0-beta.63

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 (95) hide show
  1. package/dist/{defineMacro-BLIR6k-j.d.ts → defineMacro-BATi7xoC.d.ts} +11 -20
  2. package/dist/{defineMacro-Bv9R_9a2.cjs → defineMacro-CEZHaCXE.cjs} +13 -21
  3. package/dist/defineMacro-CEZHaCXE.cjs.map +1 -0
  4. package/dist/{defineMacro-BTXvS8nI.js → defineMacro-Shz8f6SG.js} +12 -19
  5. package/dist/defineMacro-Shz8f6SG.js.map +1 -0
  6. package/dist/factory.cjs +88 -28
  7. package/dist/factory.cjs.map +1 -0
  8. package/dist/factory.d.ts +6 -41
  9. package/dist/factory.js +58 -3
  10. package/dist/factory.js.map +1 -0
  11. package/dist/{index-BzjwdK2M.d.ts → index-B9cc8MBS.d.ts} +164 -194
  12. package/dist/index.cjs +144 -274
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +43 -37
  15. package/dist/index.js +109 -235
  16. package/dist/index.js.map +1 -1
  17. package/dist/macros.cjs +41 -28
  18. package/dist/macros.cjs.map +1 -1
  19. package/dist/macros.d.ts +14 -12
  20. package/dist/macros.js +34 -21
  21. package/dist/macros.js.map +1 -1
  22. package/dist/{operationParams-BZ07xDm0.d.ts → operationParams-k5CKwSWZ.d.ts} +9 -2
  23. package/dist/{visitor-DJ6ZEJvq.js → refs-BjNDuCBD.js} +116 -159
  24. package/dist/refs-BjNDuCBD.js.map +1 -0
  25. package/dist/{visitor-DpKZ9Tk0.cjs → refs-u5SDdyV7.cjs} +140 -195
  26. package/dist/refs-u5SDdyV7.cjs.map +1 -0
  27. package/dist/{response-hnSw2NKE.cjs → schema-BkvrrOAr.cjs} +496 -231
  28. package/dist/schema-BkvrrOAr.cjs.map +1 -0
  29. package/dist/{response-KUdWiDWw.js → schema-Cbnxmz4b.js} +472 -220
  30. package/dist/schema-Cbnxmz4b.js.map +1 -0
  31. package/dist/{types-DyDzizSf.d.ts → types-BB_xgRJ3.d.ts} +49 -123
  32. package/dist/types.d.ts +5 -6
  33. package/dist/{utils-BLJwyza-.cjs → utils-CEepwqmb.cjs} +76 -69
  34. package/dist/utils-CEepwqmb.cjs.map +1 -0
  35. package/dist/{utils-CF_-Pn_c.js → utils-DaXkewb1.js} +58 -51
  36. package/dist/utils-DaXkewb1.js.map +1 -0
  37. package/dist/utils.cjs +11 -11
  38. package/dist/utils.d.ts +53 -30
  39. package/dist/utils.js +3 -3
  40. package/package.json +1 -1
  41. package/src/constants.ts +3 -36
  42. package/src/defineMacro.ts +20 -13
  43. package/src/{node.ts → defineNode.ts} +12 -38
  44. package/src/{printer.ts → definePrinter.ts} +5 -3
  45. package/src/dialect.ts +52 -19
  46. package/src/factory.ts +7 -94
  47. package/src/index.ts +7 -9
  48. package/src/infer.ts +1 -1
  49. package/src/macros/macroDiscriminatorEnum.ts +7 -1
  50. package/src/macros/macroEnumName.ts +10 -2
  51. package/src/macros/macroSimplifyUnion.ts +29 -19
  52. package/src/nodes/code.ts +40 -80
  53. package/src/nodes/content.ts +2 -7
  54. package/src/nodes/file.ts +107 -18
  55. package/src/nodes/function.ts +46 -46
  56. package/src/nodes/index.ts +2 -3
  57. package/src/nodes/input.ts +13 -9
  58. package/src/nodes/operation.ts +8 -5
  59. package/src/nodes/output.ts +1 -1
  60. package/src/nodes/parameter.ts +5 -5
  61. package/src/nodes/property.ts +5 -5
  62. package/src/nodes/requestBody.ts +6 -13
  63. package/src/nodes/response.ts +93 -8
  64. package/src/nodes/schema.ts +5 -5
  65. package/src/optionality.ts +15 -0
  66. package/src/registry.ts +1 -1
  67. package/src/types.ts +4 -6
  68. package/src/utils/codegen.ts +6 -7
  69. package/src/utils/extractStringsFromNodes.ts +1 -0
  70. package/src/utils/fileMerge.ts +7 -8
  71. package/src/utils/operationParams.ts +9 -9
  72. package/src/utils/refs.ts +14 -18
  73. package/src/utils/schemaGraph.ts +41 -33
  74. package/src/utils/schemaMerge.ts +2 -2
  75. package/src/visitor.ts +3 -14
  76. package/dist/defineMacro-BTXvS8nI.js.map +0 -1
  77. package/dist/defineMacro-Bv9R_9a2.cjs.map +0 -1
  78. package/dist/extractStringsFromNodes-Cja-xxx5.js +0 -29
  79. package/dist/extractStringsFromNodes-Cja-xxx5.js.map +0 -1
  80. package/dist/extractStringsFromNodes-DKgDjFO0.cjs +0 -34
  81. package/dist/extractStringsFromNodes-DKgDjFO0.cjs.map +0 -1
  82. package/dist/extractStringsFromNodes-p4mX1TQD.d.ts +0 -14
  83. package/dist/factory-CZNOGI-N.js +0 -283
  84. package/dist/factory-CZNOGI-N.js.map +0 -1
  85. package/dist/factory-DG1CVkEb.cjs +0 -300
  86. package/dist/factory-DG1CVkEb.cjs.map +0 -1
  87. package/dist/response-KUdWiDWw.js.map +0 -1
  88. package/dist/response-hnSw2NKE.cjs.map +0 -1
  89. package/dist/utils-BLJwyza-.cjs.map +0 -1
  90. package/dist/utils-CF_-Pn_c.js.map +0 -1
  91. package/dist/visitor-DJ6ZEJvq.js.map +0 -1
  92. package/dist/visitor-DpKZ9Tk0.cjs.map +0 -1
  93. package/src/dedupe.ts +0 -239
  94. package/src/mocks.ts +0 -56
  95. package/src/nodes/http.ts +0 -85
package/src/dialect.ts CHANGED
@@ -1,53 +1,86 @@
1
+ import type { Node } 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
+
31
+ /**
32
+ * How a dialect collapses structurally identical schemas into shared definitions. The contract is
33
+ * generic over the plan and context types, which the adapter supplies. The mechanics live in the
34
+ * adapter, not here, so `@kubb/ast` carries no dedupe logic. The returned plan owns the rewriting
35
+ * behavior, so callers interact with one object.
36
+ */
37
+ export type Dedupe<TPlan = unknown, TContext = unknown> = {
38
+ /**
39
+ * Scans a forest of nodes and produces a plan describing which shapes to share.
40
+ */
41
+ plan(roots: ReadonlyArray<Node>, context: TContext): TPlan
42
+ }
43
+
44
+ /**
45
+ * A spec adapter's dialect. `name` identifies it in logs and diagnostics, `schema` holds the
46
+ * spec-specific schema questions the parser answers, and `dedupe` is the schema-sharing seam.
47
+ */
48
+ export type Dialect<TSchema = unknown, TRef = TSchema, TDiscriminated = TSchema, TDocument = unknown, TDedupe extends Dedupe = Dedupe> = {
49
+ /**
50
+ * Identifies the dialect in logs and diagnostics.
51
+ */
52
+ name: string
53
+ /**
54
+ * The spec-specific schema behavior. See {@link SchemaDialect}.
55
+ */
56
+ schema: SchemaDialect<TSchema, TRef, TDiscriminated, TDocument>
57
+ /**
58
+ * The schema-sharing behavior. See {@link Dedupe}.
59
+ */
60
+ dedupe: TDedupe
31
61
  }
32
62
 
33
63
  /**
34
- * Types a {@link SchemaDialect} for an adapter. Adds no runtime behavior and only pins the
64
+ * Types a {@link Dialect} for an adapter. Adds no runtime behavior and only pins the
35
65
  * dialect's type for inference.
36
66
  *
37
67
  * @example
38
68
  * ```ts
39
- * export const oasDialect = defineSchemaDialect({
69
+ * export const oasDialect = defineDialect({
40
70
  * name: 'oas',
41
- * isNullable,
42
- * isReference,
43
- * isDiscriminator,
44
- * isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
45
- * resolveRef,
71
+ * schema: {
72
+ * isNullable,
73
+ * isReference,
74
+ * isDiscriminator,
75
+ * isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
76
+ * resolveRef,
77
+ * },
78
+ * dedupe: { plan },
46
79
  * })
47
80
  * ```
48
81
  */
49
- export function defineSchemaDialect<TSchema, TRef, TDiscriminated, TDocument>(
50
- dialect: SchemaDialect<TSchema, TRef, TDiscriminated, TDocument>,
51
- ): SchemaDialect<TSchema, TRef, TDiscriminated, TDocument> {
82
+ export function defineDialect<TSchema, TRef, TDiscriminated, TDocument, TDedupe extends Dedupe>(
83
+ dialect: Dialect<TSchema, TRef, TDiscriminated, TDocument, TDedupe>,
84
+ ): Dialect<TSchema, TRef, TDiscriminated, TDocument, TDedupe> {
52
85
  return dialect
53
86
  }
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'
@@ -26,10 +22,10 @@ export { createSchema } from './nodes/schema.ts'
26
22
  * `changes` already equals (by reference) the current value, otherwise a new node
27
23
  * with the changes applied.
28
24
  *
29
- * Mirrors the TypeScript compiler's `factory.updateX` contract, pair it with the
30
- * structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
31
- * downstream passes can detect "nothing changed" by identity. Comparison is
32
- * shallow: a structurally-equal but newly-allocated array/object counts as a change.
25
+ * Mirrors the TypeScript compiler's `factory.updateX` contract. Pair it with the
26
+ * structural sharing in {@link transform} so a no-op rewrite does not allocate and
27
+ * downstream passes can detect "nothing changed" by identity. Comparison is shallow,
28
+ * so a structurally equal but newly allocated array or object counts as a change.
33
29
  *
34
30
  * @example
35
31
  * ```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,15 +1,13 @@
1
- export { httpMethods, schemaTypes } from './constants.ts'
2
- export { applyDedupe, buildDedupePlan } from './dedupe.ts'
3
- export { defineSchemaDialect } from './dialect.ts'
4
- export * as factory from './factory.ts'
1
+ export { schemaTypes } from './constants.ts'
2
+ export { defineDialect } from './dialect.ts'
5
3
  export { isHttpOperationNode, narrowSchema } from './guards.ts'
6
4
  export { applyMacros, composeMacros, defineMacro } from './defineMacro.ts'
7
- export { defineNode } from './node.ts'
8
- export { syncOptionality } from './node.ts'
9
- export * from './registry.ts'
10
- export { createPrinterFactory, definePrinter } from './printer.ts'
5
+ export { defineNode } from './defineNode.ts'
6
+ export { optionality } from './optionality.ts'
7
+ export { createPrinterFactory, definePrinter } from './definePrinter.ts'
11
8
  export { signatureOf } from './signature.ts'
12
- export { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
13
9
  export { collect, transform, walk } from './visitor.ts'
14
10
 
11
+ export * as factory from './factory.ts'
12
+ export * from './registry.ts'
15
13
  export type * from './types.ts'
package/src/infer.ts CHANGED
@@ -16,7 +16,7 @@ import type {
16
16
  } from './nodes/index.ts'
17
17
 
18
18
  /**
19
- * Shared parser options used by OAS-to-AST inference and parser flows.
19
+ * Options that control how the adapter parser maps OpenAPI schemas to AST nodes.
20
20
  */
21
21
  export type ParserOptions = {
22
22
  /**
@@ -3,6 +3,12 @@ import { narrowSchema } from '../guards.ts'
3
3
  import { createProperty } from '../nodes/property.ts'
4
4
  import { createSchema } from '../nodes/schema.ts'
5
5
 
6
+ type Props = {
7
+ propertyName: string
8
+ values: Array<string>
9
+ enumName?: string
10
+ }
11
+
6
12
  /**
7
13
  * Builds a macro that replaces a discriminator property's schema with a string enum of the given
8
14
  * values. Object schemas that lack the property are returned unchanged.
@@ -13,7 +19,7 @@ import { createSchema } from '../nodes/schema.ts'
13
19
  * const next = applyMacros(objectSchema, [macro], { depth: 'shallow' })
14
20
  * ```
15
21
  */
16
- export function macroDiscriminatorEnum({ propertyName, values, enumName }: { propertyName: string; values: Array<string>; enumName?: string }) {
22
+ export function macroDiscriminatorEnum({ propertyName, values, enumName }: Props) {
17
23
  return defineMacro({
18
24
  name: 'discriminator-enum',
19
25
  schema(node) {
@@ -2,9 +2,15 @@ import { defineMacro } from '../defineMacro.ts'
2
2
  import { narrowSchema } from '../guards.ts'
3
3
  import { enumPropName } from '../utils/refs.ts'
4
4
 
5
+ type Props = {
6
+ parentName: string | null | undefined
7
+ propName: string
8
+ enumSuffix: string
9
+ }
10
+
5
11
  /**
6
12
  * Builds a macro that names an inline enum schema from its parent and property name. Boolean enums
7
- * are left anonymous; non-enum nodes are returned unchanged.
13
+ * are left anonymous. Non-enum nodes are returned unchanged.
8
14
  *
9
15
  * @example
10
16
  * ```ts
@@ -12,13 +18,15 @@ import { enumPropName } from '../utils/refs.ts'
12
18
  * const named = applyMacros(propSchema, [macro], { depth: 'shallow' })
13
19
  * ```
14
20
  */
15
- export function macroEnumName({ parentName, propName, enumSuffix }: { parentName: string | null | undefined; propName: string; enumSuffix: string }) {
21
+ export function macroEnumName({ parentName, propName, enumSuffix }: Props) {
16
22
  return defineMacro({
17
23
  name: 'enum-name',
18
24
  schema(node) {
19
25
  const enumNode = narrowSchema(node, 'enum')
26
+
20
27
  if (enumNode?.primitive === 'boolean') return { ...node, name: null }
21
28
  if (enumNode) return { ...node, name: enumPropName(parentName, propName, enumSuffix) }
29
+
22
30
  return undefined
23
31
  },
24
32
  })
@@ -1,29 +1,17 @@
1
- import { isScalarPrimitive } from '../constants.ts'
2
1
  import { defineMacro } from '../defineMacro.ts'
3
2
  import { narrowSchema } from '../guards.ts'
4
3
  import type { SchemaNode } from '../nodes/schema.ts'
5
4
 
5
+ type ScalarPrimitive = 'string' | 'number' | 'integer' | 'bigint' | 'boolean'
6
+
6
7
  /**
7
- * Removes union members a broader scalar primitive already covers, such as a multi-value string enum
8
- * sitting next to a plain `string`. Single-value enums are kept.
9
- *
10
- * @example
11
- * ```ts
12
- * const next = applyMacros(unionSchema, [macroSimplifyUnion], { depth: 'shallow' })
13
- * ```
8
+ * Scalar primitive schema types used for union simplification and type narrowing.
14
9
  */
15
- export const macroSimplifyUnion = defineMacro({
16
- name: 'simplify-union',
17
- schema(node) {
18
- const unionNode = narrowSchema(node, 'union')
19
- if (!unionNode?.members?.length) return undefined
20
-
21
- const simplified = simplifyUnionMembers(unionNode.members)
22
- if (simplified.length === unionNode.members.length) return undefined
10
+ const SCALAR_PRIMITIVE_TYPES = new Set<ScalarPrimitive>(['string', 'number', 'integer', 'bigint', 'boolean'])
23
11
 
24
- return { ...unionNode, members: simplified }
25
- },
26
- })
12
+ function isScalarPrimitive(type: string): type is ScalarPrimitive {
13
+ return SCALAR_PRIMITIVE_TYPES.has(type as ScalarPrimitive)
14
+ }
27
15
 
28
16
  /**
29
17
  * Filters union members, dropping enum members that a broader scalar primitive already covers.
@@ -48,3 +36,25 @@ function simplifyUnionMembers(members: Array<SchemaNode>): Array<SchemaNode> {
48
36
  return true
49
37
  })
50
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
+ })
package/src/nodes/code.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
3
 
4
4
  /**
@@ -116,7 +116,10 @@ export type FunctionNode = BaseNode & {
116
116
  */
117
117
  default?: boolean | null
118
118
  /**
119
- * Function parameter list rendered as a string (e.g. from `FunctionParams.toConstructor()`).
119
+ * Function parameter list as a pre-rendered string, written verbatim between the parentheses.
120
+ *
121
+ * @example
122
+ * `'id: string, config: Config = {}'`
120
123
  */
121
124
  params?: string | null
122
125
  /**
@@ -164,55 +167,12 @@ export type FunctionNode = BaseNode & {
164
167
  * // export const getPet = () => ...
165
168
  * ```
166
169
  */
167
- export type ArrowFunctionNode = BaseNode & {
170
+ export type ArrowFunctionNode = Omit<FunctionNode, 'kind'> & {
168
171
  kind: 'ArrowFunction'
169
- /**
170
- * Name of the arrow function (used as the `const` variable name).
171
- */
172
- name: string
173
- /**
174
- * Whether the function is a default export.
175
- */
176
- default?: boolean | null
177
- /**
178
- * Function parameter list rendered as a string (e.g. from `FunctionParams.toConstructor()`).
179
- */
180
- params?: string | null
181
- /**
182
- * Whether the arrow function should be exported.
183
- */
184
- export?: boolean | null
185
- /**
186
- * Whether the arrow function is async. When `true`, the return type is wrapped in `Promise<>`.
187
- */
188
- async?: boolean | null
189
- /**
190
- * TypeScript generic type parameters.
191
- *
192
- * @example Constrained generics
193
- * `['T', 'U extends string']`
194
- */
195
- generics?: string | Array<string> | null
196
- /**
197
- * Return type annotation.
198
- *
199
- * @example Type reference
200
- * `'Pet'`
201
- */
202
- returnType?: string | null
203
- /**
204
- * JSDoc documentation metadata.
205
- */
206
- JSDoc?: JSDocNode | null
207
172
  /**
208
173
  * Render the arrow function body as a single-line expression.
209
174
  */
210
175
  singleLine?: boolean | null
211
- /**
212
- * Child nodes representing the function body (children of the `Function.Arrow` component).
213
- * Each entry is a {@link CodeNode}. Use {@link TextNode} for raw string content.
214
- */
215
- nodes?: Array<CodeNode>
216
176
  }
217
177
 
218
178
  /**
@@ -236,16 +196,16 @@ export type TextNode = BaseNode & {
236
196
  }
237
197
 
238
198
  /**
239
- * AST node representing a line break in the source output.
199
+ * AST node representing a blank line in the source output.
240
200
  *
241
- * Corresponds to `<br/>` in JSX components. When printed it produces an empty string,
242
- * so joining nodes with `\n` in `printNodes` leaves a blank line between the surrounding code.
201
+ * Corresponds to `<br/>` in JSX components. `printNodes` turns a `Break` between two
202
+ * statements into one blank line. Consecutive breaks, and breaks at the start or end of
203
+ * the list, are folded away, so a `Break` never produces more than one blank line.
243
204
  *
244
205
  * @example
245
206
  * ```ts
246
207
  * createBreak()
247
208
  * // { kind: 'Break' }
248
- * // prints as '' → blank line when surrounded by other nodes
249
209
  * ```
250
210
  */
251
211
  export type BreakNode = BaseNode & {
@@ -285,6 +245,36 @@ export type CodeNode = ConstNode | TypeNode | FunctionNode | ArrowFunctionNode |
285
245
  */
286
246
  export const constDef = defineNode<ConstNode>({ kind: 'Const' })
287
247
 
248
+ /**
249
+ * Definition for the {@link TypeNode}.
250
+ */
251
+ export const typeDef = defineNode<TypeNode>({ kind: 'Type' })
252
+
253
+ /**
254
+ * Definition for the {@link FunctionNode}.
255
+ */
256
+ export const functionDef = defineNode<FunctionNode>({ kind: 'Function' })
257
+
258
+ /**
259
+ * Definition for the {@link ArrowFunctionNode}.
260
+ */
261
+ export const arrowFunctionDef = defineNode<ArrowFunctionNode>({ kind: 'ArrowFunction' })
262
+
263
+ /**
264
+ * Definition for the {@link TextNode}.
265
+ */
266
+ export const textDef = defineNode<TextNode, string>({ kind: 'Text', build: (value) => ({ value }) })
267
+
268
+ /**
269
+ * Definition for the {@link BreakNode}.
270
+ */
271
+ export const breakDef = defineNode<BreakNode, void>({ kind: 'Break', build: () => ({}) })
272
+
273
+ /**
274
+ * Definition for the {@link JsxNode}.
275
+ */
276
+ export const jsxDef = defineNode<JsxNode, string>({ kind: 'Jsx', build: (value) => ({ value }) })
277
+
288
278
  /**
289
279
  * Creates a `ConstNode` representing a TypeScript `const` declaration.
290
280
  *
@@ -296,11 +286,6 @@ export const constDef = defineNode<ConstNode>({ kind: 'Const' })
296
286
  */
297
287
  export const createConst = constDef.create
298
288
 
299
- /**
300
- * Definition for the {@link TypeNode}.
301
- */
302
- export const typeDef = defineNode<TypeNode>({ kind: 'Type' })
303
-
304
289
  /**
305
290
  * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
306
291
  *
@@ -312,11 +297,6 @@ export const typeDef = defineNode<TypeNode>({ kind: 'Type' })
312
297
  */
313
298
  export const createType = typeDef.create
314
299
 
315
- /**
316
- * Definition for the {@link FunctionNode}.
317
- */
318
- export const functionDef = defineNode<FunctionNode>({ kind: 'Function' })
319
-
320
300
  /**
321
301
  * Creates a `FunctionNode` representing a TypeScript `function` declaration.
322
302
  *
@@ -328,11 +308,6 @@ export const functionDef = defineNode<FunctionNode>({ kind: 'Function' })
328
308
  */
329
309
  export const createFunction = functionDef.create
330
310
 
331
- /**
332
- * Definition for the {@link ArrowFunctionNode}.
333
- */
334
- export const arrowFunctionDef = defineNode<ArrowFunctionNode>({ kind: 'ArrowFunction' })
335
-
336
311
  /**
337
312
  * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
338
313
  *
@@ -344,11 +319,6 @@ export const arrowFunctionDef = defineNode<ArrowFunctionNode>({ kind: 'ArrowFunc
344
319
  */
345
320
  export const createArrowFunction = arrowFunctionDef.create
346
321
 
347
- /**
348
- * Definition for the {@link TextNode}.
349
- */
350
- export const textDef = defineNode<TextNode, string>({ kind: 'Text', build: (value) => ({ value }) })
351
-
352
322
  /**
353
323
  * Creates a {@link TextNode} representing a raw string fragment in the source output.
354
324
  *
@@ -360,11 +330,6 @@ export const textDef = defineNode<TextNode, string>({ kind: 'Text', build: (valu
360
330
  */
361
331
  export const createText = textDef.create
362
332
 
363
- /**
364
- * Definition for the {@link BreakNode}.
365
- */
366
- export const breakDef = defineNode<BreakNode, void>({ kind: 'Break', build: () => ({}) })
367
-
368
333
  /**
369
334
  * Creates a {@link BreakNode} representing a line break in the source output.
370
335
  *
@@ -378,11 +343,6 @@ export function createBreak(): BreakNode {
378
343
  return breakDef.create()
379
344
  }
380
345
 
381
- /**
382
- * Definition for the {@link JsxNode}.
383
- */
384
- export const jsxDef = defineNode<JsxNode, string>({ kind: 'Jsx', build: (value) => ({ value }) })
385
-
386
346
  /**
387
347
  * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
388
348
  *
@@ -1,4 +1,4 @@
1
- import { defineNode } from '../node.ts'
1
+ import { defineNode } from '../defineNode.ts'
2
2
  import type { BaseNode } from './base.ts'
3
3
  import type { SchemaNode } from './schema.ts'
4
4
 
@@ -37,15 +37,10 @@ export type ContentNode = BaseNode & {
37
37
  keysToOmit?: Array<string> | null
38
38
  }
39
39
 
40
- /**
41
- * Loosely-typed content entry accepted by the builders, normalized into a {@link ContentNode}.
42
- */
43
- export type UserContent = Omit<ContentNode, 'kind'>
44
-
45
40
  /**
46
41
  * Definition for the {@link ContentNode}.
47
42
  */
48
- export const contentDef = defineNode<ContentNode, UserContent>({
43
+ export const contentDef = defineNode<ContentNode>({
49
44
  kind: 'Content',
50
45
  children: ['schema'],
51
46
  })