@kubb/ast 5.0.0-beta.57 → 5.0.0-beta.59
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/README.md +21 -7
- package/dist/casing-BE2R1RXg.cjs +88 -0
- package/dist/casing-BE2R1RXg.cjs.map +1 -0
- package/dist/chunk-CNktS9qV.js +17 -0
- package/dist/extractStringsFromNodes-WMYJ8nQL.d.ts +82 -0
- package/dist/factory-BmcGBdeg.cjs +1251 -0
- package/dist/factory-BmcGBdeg.cjs.map +1 -0
- package/dist/factory-Du7nEP4B.js +282 -0
- package/dist/factory-Du7nEP4B.js.map +1 -0
- package/dist/factory.cjs +28 -0
- package/dist/factory.d.ts +62 -0
- package/dist/factory.js +3 -0
- package/dist/{types-C5aVnRE1.d.ts → index-BzjwdK2M.d.ts} +94 -1146
- package/dist/index.cjs +220 -1481
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +96 -58
- package/dist/index.js +182 -1920
- package/dist/index.js.map +1 -1
- package/dist/operationParams-BZ07xDm0.d.ts +204 -0
- package/dist/response-DKxTr522.js +683 -0
- package/dist/response-DKxTr522.js.map +1 -0
- package/dist/types-olVl9v5p.d.ts +764 -0
- package/dist/types.d.ts +5 -2
- package/dist/utils-BCtRXfhI.cjs +275 -0
- package/dist/utils-BCtRXfhI.cjs.map +1 -0
- package/dist/utils-SdZU0F3H.js +1336 -0
- package/dist/utils-SdZU0F3H.js.map +1 -0
- package/dist/utils.cjs +129 -13
- package/dist/utils.d.ts +127 -76
- package/dist/utils.js +3 -2
- package/package.json +5 -1
- package/src/constants.ts +8 -14
- package/src/dedupe.ts +8 -0
- package/src/factory.ts +18 -1
- package/src/guards.ts +1 -1
- package/src/index.ts +7 -50
- package/src/node.ts +1 -1
- package/src/nodes/base.ts +0 -10
- package/src/nodes/code.ts +29 -47
- package/src/nodes/content.ts +2 -2
- package/src/nodes/file.ts +28 -23
- package/src/nodes/function.ts +2 -17
- package/src/nodes/index.ts +1 -1
- package/src/nodes/input.ts +20 -19
- package/src/nodes/operation.ts +1 -1
- package/src/nodes/parameter.ts +0 -3
- package/src/nodes/property.ts +0 -3
- package/src/nodes/requestBody.ts +3 -6
- package/src/nodes/schema.ts +3 -2
- package/src/printer.ts +4 -4
- package/src/registry.ts +31 -26
- package/src/signature.ts +3 -3
- package/src/transformers.ts +28 -1
- package/src/types.ts +2 -53
- package/src/utils/codegen.ts +104 -0
- package/src/utils/extractStringsFromNodes.ts +34 -0
- package/src/utils/fileMerge.ts +184 -0
- package/src/utils/index.ts +8 -296
- package/src/utils/operationParams.ts +353 -0
- package/src/utils/refs.ts +112 -0
- package/src/utils/schemaGraph.ts +169 -0
- package/src/utils/strings.ts +139 -0
- package/src/visitor.ts +43 -19
- package/dist/chunk-C0LytTxp.js +0 -8
- package/dist/utils-0p8ZO287.js +0 -570
- package/dist/utils-0p8ZO287.js.map +0 -1
- package/dist/utils-cdQ6Pzyi.cjs +0 -726
- package/dist/utils-cdQ6Pzyi.cjs.map +0 -1
- package/src/utils/ast.ts +0 -879
package/src/nodes/input.ts
CHANGED
|
@@ -35,11 +35,11 @@ export type InputMeta = {
|
|
|
35
35
|
baseURL?: string | null
|
|
36
36
|
/**
|
|
37
37
|
* Names of schemas that participate in a circular reference chain.
|
|
38
|
-
* Computed once during the adapter pre-scan,
|
|
39
|
-
* `findCircularSchemas`
|
|
38
|
+
* Computed once during the adapter pre-scan, so a generator never has to
|
|
39
|
+
* call `findCircularSchemas` itself.
|
|
40
40
|
*
|
|
41
41
|
* Convert to a `Set` once at the start of a generator, not per-schema,
|
|
42
|
-
*
|
|
42
|
+
* so lookups stay O(1) without repeated allocations.
|
|
43
43
|
*
|
|
44
44
|
* @example Wrap a circular schema in z.lazy()
|
|
45
45
|
* ```ts
|
|
@@ -50,11 +50,11 @@ export type InputMeta = {
|
|
|
50
50
|
circularNames: ReadonlyArray<string>
|
|
51
51
|
/**
|
|
52
52
|
* Names of schemas whose type is `enum`.
|
|
53
|
-
* Computed once during the adapter pre-scan,
|
|
54
|
-
*
|
|
53
|
+
* Computed once during the adapter pre-scan, so a generator never has to
|
|
54
|
+
* filter the schema list itself.
|
|
55
55
|
*
|
|
56
56
|
* Convert to a `Set` once at the start of a generator when you need repeated
|
|
57
|
-
* membership checks,
|
|
57
|
+
* membership checks, so each check stays O(1) instead of an array scan.
|
|
58
58
|
*
|
|
59
59
|
* @example Check if a referenced schema is an enum
|
|
60
60
|
* `const enums = new Set(meta.enumNames)`
|
|
@@ -114,26 +114,27 @@ export const inputDef = defineNode<InputNode, Partial<Omit<InputNode, 'kind'>>>(
|
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
/**
|
|
117
|
-
* Creates an `InputNode
|
|
117
|
+
* Creates an `InputNode`. Pass `stream: true` for the streaming variant whose `schemas` and
|
|
118
|
+
* `operations` are `AsyncIterable` sources and whose `meta` is optional. Otherwise it builds the
|
|
119
|
+
* eager variant with array `schemas`/`operations` and the defaulted `meta`.
|
|
118
120
|
*
|
|
119
|
-
* @example
|
|
121
|
+
* @example Eager
|
|
120
122
|
* ```ts
|
|
121
123
|
* const input = createInput()
|
|
122
124
|
* // { kind: 'Input', schemas: [], operations: [] }
|
|
123
125
|
* ```
|
|
124
|
-
*/
|
|
125
|
-
export function createInput(overrides: Partial<Omit<InputNode, 'kind'>> = {}): InputNode {
|
|
126
|
-
return inputDef.create(overrides)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Creates a streaming `InputNode<true>` from pre-built `AsyncIterable` sources.
|
|
131
126
|
*
|
|
132
|
-
* @example
|
|
127
|
+
* @example Streaming
|
|
133
128
|
* ```ts
|
|
134
|
-
* const node =
|
|
129
|
+
* const node = createInput({ stream: true, schemas: schemasIterable, operations: operationsIterable, meta: { title: 'My API' } })
|
|
135
130
|
* ```
|
|
136
131
|
*/
|
|
137
|
-
export function
|
|
138
|
-
|
|
132
|
+
export function createInput<Stream extends boolean = false>(options: Partial<Omit<InputNode<Stream>, 'kind'>> & { stream?: Stream } = {}): InputNode<Stream> {
|
|
133
|
+
const { stream, ...overrides } = options
|
|
134
|
+
// Streaming inputs carry AsyncIterable sources, so skip the array/meta defaults that
|
|
135
|
+
// inputDef.create applies for the eager variant.
|
|
136
|
+
if (stream) {
|
|
137
|
+
return { kind: 'Input', ...overrides } as InputNode<Stream>
|
|
138
|
+
}
|
|
139
|
+
return inputDef.create(overrides as Partial<Omit<InputNode, 'kind'>>) as InputNode<Stream>
|
|
139
140
|
}
|
package/src/nodes/operation.ts
CHANGED
|
@@ -41,7 +41,7 @@ type OperationNodeBase = BaseNode & {
|
|
|
41
41
|
*/
|
|
42
42
|
deprecated?: boolean
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Query, path, header, and cookie parameters for the operation.
|
|
45
45
|
*/
|
|
46
46
|
parameters: Array<ParameterNode>
|
|
47
47
|
/**
|
package/src/nodes/parameter.ts
CHANGED
package/src/nodes/property.ts
CHANGED
package/src/nodes/requestBody.ts
CHANGED
|
@@ -18,12 +18,9 @@ import { type ContentNode, createContent, type UserContent } from './content.ts'
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export type RequestBodyNode = BaseNode & {
|
|
21
|
-
/**
|
|
22
|
-
* Node kind.
|
|
23
|
-
*/
|
|
24
21
|
kind: 'RequestBody'
|
|
25
22
|
/**
|
|
26
|
-
*
|
|
23
|
+
* Request body description carried over from the spec.
|
|
27
24
|
*/
|
|
28
25
|
description?: string
|
|
29
26
|
/**
|
|
@@ -32,11 +29,11 @@ export type RequestBodyNode = BaseNode & {
|
|
|
32
29
|
*/
|
|
33
30
|
required?: boolean
|
|
34
31
|
/**
|
|
35
|
-
*
|
|
32
|
+
* Content type entries for this request body.
|
|
36
33
|
*
|
|
37
34
|
* When the adapter `contentType` option is set, this array contains exactly one entry for
|
|
38
35
|
* that content type. Otherwise it contains one entry per content type declared in the spec,
|
|
39
|
-
* so
|
|
36
|
+
* so plugins can generate code for every variant (for example, separate hooks for
|
|
40
37
|
* `application/json` and `multipart/form-data`).
|
|
41
38
|
*/
|
|
42
39
|
content?: Array<ContentNode>
|
package/src/nodes/schema.ts
CHANGED
|
@@ -280,8 +280,9 @@ export type UnionSchemaNode = CompositeSchemaNodeBase & {
|
|
|
280
280
|
*/
|
|
281
281
|
discriminatorPropertyName?: string
|
|
282
282
|
/**
|
|
283
|
-
*
|
|
284
|
-
* '
|
|
283
|
+
* How many union members must be valid.
|
|
284
|
+
* - `'one'`: exactly one member, from `oneOf`
|
|
285
|
+
* - `'any'`: any number of members, from `anyOf`
|
|
285
286
|
*/
|
|
286
287
|
strategy?: 'one' | 'any'
|
|
287
288
|
}
|
package/src/printer.ts
CHANGED
|
@@ -106,7 +106,7 @@ export type Printer<T extends PrinterFactoryOptions = PrinterFactoryOptions> = {
|
|
|
106
106
|
/**
|
|
107
107
|
* Node-level dispatcher, converts a `SchemaNode` directly to `TOutput` using the `nodes` handlers.
|
|
108
108
|
* Always dispatches through the `nodes` map. Never calls the `print` override.
|
|
109
|
-
*
|
|
109
|
+
* Reach for it when you need the raw output (e.g. `ts.TypeNode`) without declaration wrapping.
|
|
110
110
|
*/
|
|
111
111
|
transform: (node: SchemaNode) => T['output'] | null
|
|
112
112
|
/**
|
|
@@ -192,11 +192,11 @@ export function definePrinter<T extends PrinterFactoryOptions = PrinterFactoryOp
|
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
194
|
* Generic printer-factory function used by `definePrinter` and `defineFunctionPrinter`.
|
|
195
|
-
|
|
195
|
+
*
|
|
196
196
|
* @example
|
|
197
197
|
* ```ts
|
|
198
|
-
* export const defineFunctionPrinter = createPrinterFactory<
|
|
199
|
-
* (node) =>
|
|
198
|
+
* export const defineFunctionPrinter = createPrinterFactory<FunctionParamNode, FunctionParamKind, Partial<Record<FunctionParamKind, FunctionParamNode>>>(
|
|
199
|
+
* (node) => node.kind,
|
|
200
200
|
* )
|
|
201
201
|
* ```
|
|
202
202
|
*/
|
package/src/registry.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { arrowFunctionDef, breakDef, constDef, functionDef, jsxDef, textDef, typ
|
|
|
3
3
|
import { contentDef } from './nodes/content.ts'
|
|
4
4
|
import { exportDef, fileDef, importDef, sourceDef } from './nodes/file.ts'
|
|
5
5
|
import { functionParameterDef, functionParametersDef, indexedAccessTypeDef, objectBindingPatternDef, typeLiteralDef } from './nodes/function.ts'
|
|
6
|
-
import type { Node, NodeKind } from './nodes/index.ts'
|
|
7
6
|
import { inputDef } from './nodes/input.ts'
|
|
8
7
|
import { operationDef } from './nodes/operation.ts'
|
|
9
8
|
import { outputDef } from './nodes/output.ts'
|
|
@@ -13,9 +12,39 @@ import { requestBodyDef } from './nodes/requestBody.ts'
|
|
|
13
12
|
import { responseDef } from './nodes/response.ts'
|
|
14
13
|
import { schemaDef } from './nodes/schema.ts'
|
|
15
14
|
|
|
15
|
+
// Surface every def from one place so the package barrel re-exports them with `export * from './registry.ts'`.
|
|
16
|
+
// Adding a node means adding its `defineNode` to a `nodes/*.ts` file and listing it in `nodeDefs` below, nothing else.
|
|
17
|
+
export {
|
|
18
|
+
arrowFunctionDef,
|
|
19
|
+
breakDef,
|
|
20
|
+
constDef,
|
|
21
|
+
contentDef,
|
|
22
|
+
exportDef,
|
|
23
|
+
fileDef,
|
|
24
|
+
functionDef,
|
|
25
|
+
functionParameterDef,
|
|
26
|
+
functionParametersDef,
|
|
27
|
+
importDef,
|
|
28
|
+
indexedAccessTypeDef,
|
|
29
|
+
inputDef,
|
|
30
|
+
jsxDef,
|
|
31
|
+
objectBindingPatternDef,
|
|
32
|
+
operationDef,
|
|
33
|
+
outputDef,
|
|
34
|
+
parameterDef,
|
|
35
|
+
propertyDef,
|
|
36
|
+
requestBodyDef,
|
|
37
|
+
responseDef,
|
|
38
|
+
schemaDef,
|
|
39
|
+
sourceDef,
|
|
40
|
+
textDef,
|
|
41
|
+
typeDef,
|
|
42
|
+
typeLiteralDef,
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
/**
|
|
17
46
|
* Every node definition. Adding a node means adding its `defineNode` to one
|
|
18
|
-
* `nodes/*.ts` file and listing it here. The visitor tables
|
|
47
|
+
* `nodes/*.ts` file and listing it here. The visitor tables in `visitor.ts` derive from it.
|
|
19
48
|
*/
|
|
20
49
|
export const nodeDefs = [
|
|
21
50
|
inputDef,
|
|
@@ -44,27 +73,3 @@ export const nodeDefs = [
|
|
|
44
73
|
sourceDef,
|
|
45
74
|
fileDef,
|
|
46
75
|
] satisfies ReadonlyArray<NodeDef>
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
|
|
50
|
-
* Derived from each definition's `children`.
|
|
51
|
-
*/
|
|
52
|
-
export const VISITOR_KEYS = Object.fromEntries(nodeDefs.flatMap((def) => (def.children ? [[def.kind, def.children] as const] : []))) as Partial<
|
|
53
|
-
Record<NodeKind, ReadonlyArray<string>>
|
|
54
|
-
>
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Maps a node kind to the matching visitor callback name. Derived from each
|
|
58
|
-
* definition's `visitorKey`.
|
|
59
|
-
*/
|
|
60
|
-
export const VISITOR_KEY_BY_KIND = Object.fromEntries(nodeDefs.flatMap((def) => (def.visitorKey ? [[def.kind, def.visitorKey] as const] : []))) as Partial<
|
|
61
|
-
Record<NodeKind, NonNullable<NodeDef['visitorKey']>>
|
|
62
|
-
>
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Per-kind builders rerun after children are rebuilt. Derived from each
|
|
66
|
-
* definition's `rebuild` flag.
|
|
67
|
-
*/
|
|
68
|
-
export const nodeRebuilders = Object.fromEntries(
|
|
69
|
-
nodeDefs.flatMap((def) => (def.rebuild ? [[def.kind, def.create as unknown as (node: Node) => Node] as const] : [])),
|
|
70
|
-
) as Partial<Record<NodeKind, (node: Node) => Node>>
|
package/src/signature.ts
CHANGED
|
@@ -170,9 +170,9 @@ function describeShape(node: SchemaNode): string {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
|
-
*
|
|
174
|
-
* hashed during dedupe planning is not walked again when it is rewritten during streaming.
|
|
175
|
-
* is safe because a signature depends only on content, and nodes are immutable once created.
|
|
173
|
+
* Caches the digest per node, keyed by identity. A `WeakMap` so entries die with the node, and so
|
|
174
|
+
* a tree hashed during dedupe planning is not walked again when it is rewritten during streaming.
|
|
175
|
+
* Reuse is safe because a signature depends only on content, and nodes are immutable once created.
|
|
176
176
|
*/
|
|
177
177
|
const signatureCache = new WeakMap<SchemaNode, string>()
|
|
178
178
|
|
package/src/transformers.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { isScalarPrimitive } from './constants.ts'
|
|
|
2
2
|
import { narrowSchema } from './guards.ts'
|
|
3
3
|
import { createProperty } from './nodes/property.ts'
|
|
4
4
|
import { createSchema, type SchemaNode } from './nodes/schema.ts'
|
|
5
|
-
import { enumPropName } from './utils/
|
|
5
|
+
import { enumPropName } from './utils/refs.ts'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
@@ -162,3 +162,30 @@ export function setEnumName(propNode: SchemaNode, parentName: string | null | un
|
|
|
162
162
|
|
|
163
163
|
return propNode
|
|
164
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merges a ref node with its resolved schema, giving usage-site fields precedence.
|
|
168
|
+
*
|
|
169
|
+
* Usage-site fields (`description`, `readOnly`, `nullable`, `deprecated`) on the ref node override
|
|
170
|
+
* the same fields in the resolved `node.schema`. Non-ref nodes are returned unchanged.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* // Ref with description override
|
|
175
|
+
* const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A cute pet' })
|
|
176
|
+
* const merged = syncSchemaRef(ref) // merges with resolved Pet schema
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export function syncSchemaRef(node: SchemaNode): SchemaNode {
|
|
180
|
+
const ref = narrowSchema(node, 'ref')
|
|
181
|
+
|
|
182
|
+
if (!ref) return node
|
|
183
|
+
if (!ref.schema) return node
|
|
184
|
+
|
|
185
|
+
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref
|
|
186
|
+
|
|
187
|
+
// Filter out undefined override values so they don't shadow the resolved schema's fields.
|
|
188
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined))
|
|
189
|
+
|
|
190
|
+
return createSchema({ ...ref.schema, ...definedOverrides })
|
|
191
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -2,59 +2,8 @@ export type { DedupeCanonical, DedupeLookups, DedupePlan } from './dedupe.ts'
|
|
|
2
2
|
export type { SchemaDialect } from './dialect.ts'
|
|
3
3
|
export type { DistributiveOmit } from './node.ts'
|
|
4
4
|
export type { InferSchemaNode, ParserOptions } from './infer.ts'
|
|
5
|
-
export type
|
|
6
|
-
ArraySchemaNode,
|
|
7
|
-
ArrowFunctionNode,
|
|
8
|
-
CodeNode,
|
|
9
|
-
ConstNode,
|
|
10
|
-
DateSchemaNode,
|
|
11
|
-
DatetimeSchemaNode,
|
|
12
|
-
EnumSchemaNode,
|
|
13
|
-
ExportNode,
|
|
14
|
-
FileNode,
|
|
15
|
-
FunctionNode,
|
|
16
|
-
FunctionNodeType,
|
|
17
|
-
FunctionParameterNode,
|
|
18
|
-
FunctionParametersNode,
|
|
19
|
-
FunctionParamNode,
|
|
20
|
-
HttpMethod,
|
|
21
|
-
HttpOperationNode,
|
|
22
|
-
ImportNode,
|
|
23
|
-
IndexedAccessTypeNode,
|
|
24
|
-
InputMeta,
|
|
25
|
-
InputNode,
|
|
26
|
-
IntersectionSchemaNode,
|
|
27
|
-
JSDocNode,
|
|
28
|
-
JsxNode,
|
|
29
|
-
Node,
|
|
30
|
-
NodeKind,
|
|
31
|
-
NumberSchemaNode,
|
|
32
|
-
ObjectBindingPatternNode,
|
|
33
|
-
ObjectSchemaNode,
|
|
34
|
-
OperationNode,
|
|
35
|
-
OutputNode,
|
|
36
|
-
ParameterLocation,
|
|
37
|
-
ParameterNode,
|
|
38
|
-
PrimitiveSchemaType,
|
|
39
|
-
PropertyNode,
|
|
40
|
-
RefSchemaNode,
|
|
41
|
-
ResponseNode,
|
|
42
|
-
ScalarSchemaType,
|
|
43
|
-
SchemaNode,
|
|
44
|
-
SchemaNodeByType,
|
|
45
|
-
SchemaType,
|
|
46
|
-
SourceNode,
|
|
47
|
-
StatusCode,
|
|
48
|
-
StringSchemaNode,
|
|
49
|
-
TextNode,
|
|
50
|
-
TimeSchemaNode,
|
|
51
|
-
TypeExpression,
|
|
52
|
-
TypeLiteralNode,
|
|
53
|
-
TypeNode,
|
|
54
|
-
UnionSchemaNode,
|
|
55
|
-
UrlSchemaNode,
|
|
56
|
-
} from './nodes/index.ts'
|
|
5
|
+
export type * from './nodes/index.ts'
|
|
57
6
|
export type { ParentOf, Visitor, VisitorContext } from './visitor.ts'
|
|
58
7
|
export type { Printer, PrinterFactoryOptions, PrinterPartial } from './printer.ts'
|
|
59
|
-
export type { OperationParamsResolver } from './utils/
|
|
8
|
+
export type { OperationParamsResolver } from './utils/operationParams.ts'
|
|
60
9
|
export type { UserFileNode } from './factory.ts'
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isIdentifier, singleQuote } from '@internals/utils'
|
|
2
|
+
import { INDENT } from '../constants.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a JSDoc comment block from an array of lines, returning `fallback` when there are no
|
|
6
|
+
* comments so callers always get a usable string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* buildJSDoc(['@type string', '@example hello'])
|
|
11
|
+
* // '/**\n * @type string\n * @example hello\n *\/\n '
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function buildJSDoc(
|
|
15
|
+
comments: Array<string>,
|
|
16
|
+
options: {
|
|
17
|
+
/**
|
|
18
|
+
* String used to indent each comment line.
|
|
19
|
+
* @default ' * '
|
|
20
|
+
*/
|
|
21
|
+
indent?: string
|
|
22
|
+
/**
|
|
23
|
+
* String appended after the closing tag.
|
|
24
|
+
* @default '\n '
|
|
25
|
+
*/
|
|
26
|
+
suffix?: string
|
|
27
|
+
/**
|
|
28
|
+
* Returned as-is when `comments` is empty.
|
|
29
|
+
* @default ' '
|
|
30
|
+
*/
|
|
31
|
+
fallback?: string
|
|
32
|
+
} = {},
|
|
33
|
+
): string {
|
|
34
|
+
const { indent = ' * ', suffix = '\n ', fallback = ' ' } = options
|
|
35
|
+
|
|
36
|
+
if (comments.length === 0) return fallback
|
|
37
|
+
|
|
38
|
+
return `/**\n${comments.map((c) => `${indent}${c}`).join('\n')}\n */${suffix}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
|
|
43
|
+
*/
|
|
44
|
+
function indentLines(text: string): string {
|
|
45
|
+
if (!text) return ''
|
|
46
|
+
return text
|
|
47
|
+
.split('\n')
|
|
48
|
+
.map((line) => (line.trim() ? `${INDENT}${line}` : ''))
|
|
49
|
+
.join('\n')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Renders an object key, quoting it with single quotes only when it is not a valid identifier.
|
|
54
|
+
* Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* objectKey('name') // 'name'
|
|
59
|
+
* objectKey('x-total') // "'x-total'"
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function objectKey(name: string): string {
|
|
63
|
+
return isIdentifier(name) ? name : singleQuote(name)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
|
|
68
|
+
* level and closing the brace at column zero. Nested objects built the same way indent cumulatively,
|
|
69
|
+
* so callers never re-parse the generated code. A trailing comma is added per entry to match the
|
|
70
|
+
* formatter's multi-line style.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
75
|
+
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function buildObject(entries: Array<string>): string {
|
|
79
|
+
if (entries.length === 0) return '{}'
|
|
80
|
+
const body = entries.map((entry) => `${indentLines(entry)},`).join('\n')
|
|
81
|
+
|
|
82
|
+
return `{\n${body}\n}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
|
|
87
|
+
* one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
|
|
88
|
+
* one level with a trailing comma and the closing bracket at column zero. Use it for `z.union([…])`,
|
|
89
|
+
* `z.array([…])`, and similar member lists so objects inside them nest correctly.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* buildList(['z.string()', 'z.number()'])
|
|
94
|
+
* // '[z.string(), z.number()]'
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function buildList(items: Array<string>, brackets: [open: string, close: string] = ['[', ']']): string {
|
|
98
|
+
const [open, close] = brackets
|
|
99
|
+
if (items.length === 0) return `${open}${close}`
|
|
100
|
+
if (!items.some((item) => item.includes('\n'))) return `${open}${items.join(', ')}${close}`
|
|
101
|
+
const body = items.map((item) => `${indentLines(item)},`).join('\n')
|
|
102
|
+
|
|
103
|
+
return `${open}\n${body}\n${close}`
|
|
104
|
+
}
|
|
@@ -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
|
+
}
|