@kubb/renderer-jsx 5.0.0-beta.16 → 5.0.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils.ts CHANGED
@@ -11,95 +11,113 @@ import {
11
11
  createText,
12
12
  createType,
13
13
  } from '@kubb/ast'
14
- import { nodeNames, TEXT_NODE_NAME } from './constants.ts'
15
- import type { DOMElement, DOMNode, ElementNames } from './types.ts'
14
+ import {
15
+ KUBB_ARROW_FUNCTION,
16
+ KUBB_CONST,
17
+ KUBB_EXPORT,
18
+ KUBB_FILE,
19
+ KUBB_FUNCTION,
20
+ KUBB_IMPORT,
21
+ KUBB_JSX,
22
+ KUBB_SOURCE,
23
+ KUBB_TYPE,
24
+ TEXT_NODE_NAME,
25
+ nodeNames,
26
+ } from './constants.ts'
27
+ import type { DOMElement, DOMNode } from './types.ts'
28
+
29
+ function toBool(val: unknown): boolean {
30
+ return (val ?? false) as boolean
31
+ }
16
32
 
17
33
  /**
18
34
  * Collect the text and nested AST-node children of a single kubb-* element.
19
35
  *
20
- * `#text` children become raw {@link TextNode}s; nested `kubb-function`, `kubb-const`,
36
+ * `#text` children become raw text nodes; nested `kubb-function`, `kubb-const`,
21
37
  * `kubb-type`, and similar elements are converted into their respective {@link CodeNode}s.
22
- * Any unrecognized DOM elements are silently skipped.
38
+ * Any unrecognized element names are silently skipped.
23
39
  */
24
- function collectChildNodes(element: DOMElement): Array<CodeNode> {
25
- const result: Array<CodeNode> = []
40
+ function collectCodeNodes(element: DOMElement): CodeNode[] {
41
+ const result: CodeNode[] = []
26
42
 
27
43
  for (const child of element.childNodes) {
28
- if (!child) {
29
- continue
30
- }
44
+ if (!child) continue
31
45
 
32
- if (child.nodeName === TEXT_NODE_NAME) {
33
- const text = (child as DOMNode<{ nodeName: '#text' }>).nodeValue
34
- if (text && text.trim().length > 0) {
35
- result.push(createText(text))
46
+ switch (child.nodeName) {
47
+ case TEXT_NODE_NAME: {
48
+ const text = (child as DOMNode<{ nodeName: '#text' }>).nodeValue
49
+ if (text && text.trim()) result.push(createText(text))
50
+ break
36
51
  }
37
- continue
38
- }
39
-
40
- if (child.nodeName === 'br') {
41
- result.push(createBreak())
42
- continue
43
- }
44
-
45
- if (child.nodeName === 'kubb-function') {
46
- const attrs = child.attributes
47
- result.push(
48
- createFunction({
49
- name: attrs.get('name') as string,
50
- params: attrs.get('params') as string | undefined,
51
- export: attrs.get('export') as boolean | undefined,
52
- default: attrs.get('default') as boolean | undefined,
53
- async: attrs.get('async') as boolean | undefined,
54
- generics: attrs.get('generics') as string | undefined,
55
- returnType: attrs.get('returnType') as string | undefined,
56
- JSDoc: attrs.get('JSDoc') as JSDocNode | undefined,
57
- nodes: collectChildNodes(child),
58
- }),
59
- )
60
- } else if (child.nodeName === 'kubb-arrow-function') {
61
- const attrs = child.attributes
62
- result.push(
63
- createArrowFunction({
64
- name: attrs.get('name') as string,
65
- params: attrs.get('params') as string | undefined,
66
- export: attrs.get('export') as boolean | undefined,
67
- default: attrs.get('default') as boolean | undefined,
68
- async: attrs.get('async') as boolean | undefined,
69
- generics: attrs.get('generics') as string | undefined,
70
- returnType: attrs.get('returnType') as string | undefined,
71
- singleLine: attrs.get('singleLine') as boolean | undefined,
72
- JSDoc: attrs.get('JSDoc') as JSDocNode | undefined,
73
- nodes: collectChildNodes(child),
74
- } as Omit<ArrowFunctionNode, 'kind'>),
75
- )
76
- } else if (child.nodeName === 'kubb-const') {
77
- const attrs = child.attributes
78
- result.push(
79
- createConst({
80
- name: attrs.get('name') as string,
81
- type: attrs.get('type') as string | undefined,
82
- export: attrs.get('export') as boolean | undefined,
83
- asConst: attrs.get('asConst') as boolean | undefined,
84
- JSDoc: attrs.get('JSDoc') as JSDocNode | undefined,
85
- nodes: collectChildNodes(child),
86
- }),
87
- )
88
- } else if (child.nodeName === 'kubb-type') {
89
- const attrs = child.attributes
90
- result.push(
91
- createType({
92
- name: attrs.get('name') as string,
93
- export: attrs.get('export') as boolean | undefined,
94
- JSDoc: attrs.get('JSDoc') as JSDocNode | undefined,
95
- nodes: collectChildNodes(child),
96
- }),
97
- )
98
- } else if (child.nodeName === 'kubb-jsx') {
99
- const textChild = child.childNodes[0]
100
- const value = textChild?.nodeName === TEXT_NODE_NAME ? (textChild as DOMNode<{ nodeName: '#text' }>).nodeValue : ''
101
- if (value) {
102
- result.push(createJsx(value))
52
+ case 'br':
53
+ result.push(createBreak())
54
+ break
55
+ case KUBB_FUNCTION: {
56
+ const attrs = child.attributes
57
+ result.push(
58
+ createFunction({
59
+ name: attrs['name'] as string,
60
+ params: attrs['params'] as string | undefined,
61
+ export: attrs['export'] as boolean | undefined,
62
+ default: attrs['default'] as boolean | undefined,
63
+ async: attrs['async'] as boolean | undefined,
64
+ generics: attrs['generics'] as string | undefined,
65
+ returnType: attrs['returnType'] as string | undefined,
66
+ JSDoc: attrs['JSDoc'] as JSDocNode | undefined,
67
+ nodes: collectCodeNodes(child),
68
+ }),
69
+ )
70
+ break
71
+ }
72
+ case KUBB_ARROW_FUNCTION: {
73
+ const attrs = child.attributes
74
+ result.push(
75
+ createArrowFunction({
76
+ name: attrs['name'] as string,
77
+ params: attrs['params'] as string | undefined,
78
+ export: attrs['export'] as boolean | undefined,
79
+ default: attrs['default'] as boolean | undefined,
80
+ async: attrs['async'] as boolean | undefined,
81
+ generics: attrs['generics'] as string | undefined,
82
+ returnType: attrs['returnType'] as string | undefined,
83
+ singleLine: attrs['singleLine'] as boolean | undefined,
84
+ JSDoc: attrs['JSDoc'] as JSDocNode | undefined,
85
+ nodes: collectCodeNodes(child),
86
+ } as Omit<ArrowFunctionNode, 'kind'>),
87
+ )
88
+ break
89
+ }
90
+ case KUBB_CONST: {
91
+ const attrs = child.attributes
92
+ result.push(
93
+ createConst({
94
+ name: attrs['name'] as string,
95
+ type: attrs['type'] as string | undefined,
96
+ export: attrs['export'] as boolean | undefined,
97
+ asConst: attrs['asConst'] as boolean | undefined,
98
+ JSDoc: attrs['JSDoc'] as JSDocNode | undefined,
99
+ nodes: collectCodeNodes(child),
100
+ }),
101
+ )
102
+ break
103
+ }
104
+ case KUBB_TYPE: {
105
+ const attrs = child.attributes
106
+ result.push(
107
+ createType({
108
+ name: attrs['name'] as string,
109
+ export: attrs['export'] as boolean | undefined,
110
+ JSDoc: attrs['JSDoc'] as JSDocNode | undefined,
111
+ nodes: collectCodeNodes(child),
112
+ }),
113
+ )
114
+ break
115
+ }
116
+ case KUBB_JSX: {
117
+ const textChild = child.childNodes[0]
118
+ const value = textChild?.nodeName === TEXT_NODE_NAME ? (textChild as DOMNode<{ nodeName: '#text' }>).nodeValue : ''
119
+ if (value) result.push(createJsx(value))
120
+ break
103
121
  }
104
122
  }
105
123
  }
@@ -108,160 +126,110 @@ function collectChildNodes(element: DOMElement): Array<CodeNode> {
108
126
  }
109
127
 
110
128
  /**
111
- * Traverse `node` and collect all `<kubb-source>` elements into a `Set<SourceNode>`.
112
- *
113
- * Elements whose `nodeName` is in `ignores` are skipped entirely (including their subtrees).
114
- * This is used to collect source blocks from a file node while excluding import/export subtrees.
129
+ * Yields every {@link SourceNode}, {@link ExportNode}, and {@link ImportNode}
130
+ * within a `<kubb-file>` subtree in a single tree walk.
115
131
  *
116
- * @example Collect sources while ignoring export and import elements
117
- * ```ts
118
- * const sources = squashSourceNodes(fileElement, ['kubb-export', 'kubb-import'])
119
- * ```
132
+ * Import and export elements are leaf nodes. Once yielded, the walker does not
133
+ * recurse into them, which also prevents source collection from descending into
134
+ * their subtrees. Dispatch on `.kind` (`'Source'`, `'Export'`, `'Import'`) to
135
+ * separate the results.
120
136
  */
121
- function squashSourceNodes(node: DOMElement, ignores: Array<ElementNames>): Set<SourceNode> {
122
- const ignoreSet = new Set(ignores)
123
- const sources = new Set<SourceNode>()
124
-
125
- const walk = (current: DOMElement): void => {
126
- for (const child of current.childNodes) {
127
- if (!child) {
128
- continue
129
- }
130
-
131
- if (child.nodeName !== TEXT_NODE_NAME && ignoreSet.has(child.nodeName)) {
132
- continue
133
- }
137
+ function* collectFileEntries(node: DOMElement): Generator<SourceNode | ExportNode | ImportNode> {
138
+ for (const child of node.childNodes) {
139
+ if (!child || child.nodeName === TEXT_NODE_NAME) continue
140
+
141
+ if (child.nodeName === KUBB_SOURCE) {
142
+ yield createSource({
143
+ name: child.attributes['name']?.toString(),
144
+ isTypeOnly: toBool(child.attributes['isTypeOnly']),
145
+ isExportable: toBool(child.attributes['isExportable']),
146
+ isIndexable: toBool(child.attributes['isIndexable']),
147
+ nodes: collectCodeNodes(child),
148
+ })
149
+ continue
150
+ }
134
151
 
135
- if (child.nodeName === 'kubb-source') {
136
- const source = createSource({
137
- name: child.attributes.get('name')?.toString(),
138
- isTypeOnly: (child.attributes.get('isTypeOnly') ?? false) as boolean,
139
- isExportable: (child.attributes.get('isExportable') ?? false) as boolean,
140
- isIndexable: (child.attributes.get('isIndexable') ?? false) as boolean,
141
- nodes: collectChildNodes(child),
142
- })
152
+ if (child.nodeName === KUBB_EXPORT) {
153
+ yield createExport({
154
+ name: child.attributes['name'] as ExportNode['name'],
155
+ path: child.attributes['path'] as string,
156
+ isTypeOnly: toBool(child.attributes['isTypeOnly']),
157
+ asAlias: toBool(child.attributes['asAlias']),
158
+ })
159
+ continue
160
+ }
143
161
 
144
- sources.add(source)
145
- continue
146
- }
162
+ if (child.nodeName === KUBB_IMPORT) {
163
+ yield createImport({
164
+ name: child.attributes['name'] as ImportNode['name'],
165
+ path: child.attributes['path'] as string,
166
+ root: child.attributes['root'] as string | undefined,
167
+ isTypeOnly: toBool(child.attributes['isTypeOnly']),
168
+ isNameSpace: toBool(child.attributes['isNameSpace']),
169
+ })
170
+ continue
171
+ }
147
172
 
148
- if (child.nodeName !== TEXT_NODE_NAME && nodeNames.has(child.nodeName)) {
149
- walk(child)
150
- }
173
+ if (nodeNames.has(child.nodeName)) {
174
+ yield* collectFileEntries(child)
151
175
  }
152
176
  }
153
-
154
- walk(node)
155
- return sources
156
177
  }
157
178
 
158
179
  /**
159
- * Traverse `node` and collect all `<kubb-export>` elements into a `Set<ExportNode>`.
180
+ * Runs a single {@link collectFileEntries} pass over a `<kubb-file>` DOM element
181
+ * and assembles the result into a {@link FileNode}, bucketing each yielded
182
+ * node by its `.kind`.
160
183
  */
161
- function squashExportNodes(node: DOMElement): Set<ExportNode> {
162
- const exports = new Set<ExportNode>()
163
-
164
- const walk = (current: DOMElement): void => {
165
- for (const child of current.childNodes) {
166
- if (!child) {
167
- continue
168
- }
169
-
170
- if (child.nodeName !== TEXT_NODE_NAME && nodeNames.has(child.nodeName)) {
171
- walk(child)
172
- }
173
-
174
- if (child.nodeName === 'kubb-export') {
175
- exports.add(
176
- createExport({
177
- name: child.attributes.get('name') as ExportNode['name'],
178
- path: child.attributes.get('path') as string,
179
- isTypeOnly: (child.attributes.get('isTypeOnly') ?? false) as boolean,
180
- asAlias: (child.attributes.get('asAlias') ?? false) as boolean,
181
- }),
182
- )
183
- }
184
- }
184
+ function createFileNode(child: DOMElement): FileNode {
185
+ const sources: SourceNode[] = []
186
+ const exports: ExportNode[] = []
187
+ const imports: ImportNode[] = []
188
+
189
+ for (const node of collectFileEntries(child)) {
190
+ if (node.kind === 'Source') sources.push(node)
191
+ else if (node.kind === 'Export') exports.push(node)
192
+ else imports.push(node)
185
193
  }
186
194
 
187
- walk(node)
188
- return exports
195
+ return {
196
+ baseName: child.attributes['baseName'],
197
+ path: child.attributes['path'],
198
+ meta: child.attributes['meta'] || {},
199
+ footer: child.attributes['footer'],
200
+ banner: child.attributes['banner'],
201
+ sources,
202
+ exports,
203
+ imports,
204
+ } as FileNode
189
205
  }
190
206
 
191
207
  /**
192
- * Traverse `node` and collect all `<kubb-import>` elements into a `Set<ImportNode>`.
208
+ * Yields each {@link FileNode} as it is encountered during the tree walk,
209
+ * without collecting into an intermediate array. Callers can begin processing
210
+ * each file before the rest of the tree is traversed.
193
211
  */
194
- function squashImportNodes(node: DOMElement): Set<ImportNode> {
195
- const imports = new Set<ImportNode>()
212
+ export function* streamFiles(node: DOMElement): Generator<FileNode> {
213
+ for (const child of node.childNodes) {
214
+ if (!child) continue
196
215
 
197
- const walk = (current: DOMElement): void => {
198
- for (const child of current.childNodes) {
199
- if (!child) {
200
- continue
201
- }
202
-
203
- if (child.nodeName !== TEXT_NODE_NAME && nodeNames.has(child.nodeName)) {
204
- walk(child)
205
- }
216
+ if (child.nodeName !== TEXT_NODE_NAME && child.nodeName !== KUBB_FILE && nodeNames.has(child.nodeName)) {
217
+ yield* streamFiles(child)
218
+ }
206
219
 
207
- if (child.nodeName === 'kubb-import') {
208
- imports.add(
209
- createImport({
210
- name: child.attributes.get('name') as ImportNode['name'],
211
- path: child.attributes.get('path') as string,
212
- root: child.attributes.get('root') as string | undefined,
213
- isTypeOnly: (child.attributes.get('isTypeOnly') ?? false) as boolean,
214
- isNameSpace: (child.attributes.get('isNameSpace') ?? false) as boolean,
215
- }),
216
- )
217
- }
220
+ if (child.nodeName === KUBB_FILE && child.attributes['baseName'] !== undefined && child.attributes['path'] !== undefined) {
221
+ yield createFileNode(child)
218
222
  }
219
223
  }
220
-
221
- walk(node)
222
- return imports
223
224
  }
224
225
 
225
226
  /**
226
227
  * Walk the virtual DOM tree rooted at `node` and convert every `<kubb-file>` element
227
228
  * into a {@link FileNode}, collecting its source blocks, imports, and exports.
228
229
  *
229
- * Returns the list of file nodes in document order. Nested files are supported
230
+ * Returns the list of file nodes in document order. Nested files are supported;
230
231
  * the walker descends into non-file elements and recurses through them.
231
232
  */
232
- export function processFiles(node: DOMElement): Array<FileNode> {
233
- const collected: Array<FileNode> = []
234
-
235
- function walk(current: DOMElement) {
236
- for (const child of current.childNodes) {
237
- if (!child) {
238
- continue
239
- }
240
-
241
- if (child.nodeName !== TEXT_NODE_NAME && child.nodeName !== 'kubb-file' && nodeNames.has(child.nodeName)) {
242
- walk(child)
243
- }
244
-
245
- if (child.nodeName === 'kubb-file') {
246
- if (child.attributes.has('baseName') && child.attributes.has('path')) {
247
- const sources = squashSourceNodes(child, ['kubb-export', 'kubb-import'])
248
-
249
- collected.push({
250
- baseName: child.attributes.get('baseName'),
251
- path: child.attributes.get('path'),
252
- meta: child.attributes.get('meta') || {},
253
- footer: child.attributes.get('footer'),
254
- banner: child.attributes.get('banner'),
255
- sources: [...sources],
256
- exports: [...squashExportNodes(child)],
257
- imports: [...squashImportNodes(child)],
258
- } as FileNode)
259
- }
260
- }
261
- }
262
- }
263
-
264
- walk(node)
265
-
266
- return collected
233
+ export function collectFiles(node: DOMElement): FileNode[] {
234
+ return [...streamFiles(node)]
267
235
  }