codama-renderers-dart 0.2.0

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.
@@ -0,0 +1,554 @@
1
+ import type { DefinedTypeNode, StructFieldTypeNode, TypeNode } from "@codama/nodes";
2
+ import { resolveNestedTypeNode } from "@codama/nodes";
3
+ import { visit } from "@codama/visitors-core";
4
+
5
+ import type { Fragment } from "../utils/fragment.js";
6
+ import {
7
+ emptyFragment,
8
+ fragment,
9
+ fragmentFromString,
10
+ mergeFragments,
11
+ use,
12
+ } from "../utils/fragment.js";
13
+ import type { RenderScope } from "../utils/options.js";
14
+ import {
15
+ camelCase,
16
+ pascalCase,
17
+ snakeCase,
18
+ } from "../utils/nameTransformers.js";
19
+
20
+ /**
21
+ * Generate a full Dart file for a defined type.
22
+ */
23
+ export function getTypePageFragment(
24
+ node: DefinedTypeNode,
25
+ scope: RenderScope,
26
+ ): Fragment {
27
+ const name = node.name as string;
28
+ const typeName = scope.nameApi.dataType(name);
29
+ const typeNode = node.type;
30
+
31
+ // Check what kind of type this is
32
+ if (typeNode.kind === "enumTypeNode") {
33
+ const isScalar = typeNode.variants.every(
34
+ (v) => v.kind === "enumEmptyVariantTypeNode",
35
+ );
36
+ if (isScalar) {
37
+ return getScalarEnumPageFragment(node, scope);
38
+ }
39
+ return getDataEnumPageFragment(node, scope);
40
+ }
41
+
42
+ if (typeNode.kind === "structTypeNode") {
43
+ return getStructPageFragment(node, scope);
44
+ }
45
+
46
+ // Simple type alias — just create codec wrappers
47
+ return getTypeAliasPageFragment(node, scope);
48
+ }
49
+
50
+ /**
51
+ * Generate a Dart scalar enum (all empty variants).
52
+ */
53
+ function getScalarEnumPageFragment(
54
+ node: DefinedTypeNode,
55
+ scope: RenderScope,
56
+ ): Fragment {
57
+ const name = node.name as string;
58
+ const typeName = scope.nameApi.dataType(name);
59
+ const enumNode = node.type;
60
+ if (enumNode.kind !== "enumTypeNode") return emptyFragment();
61
+
62
+ const variants = enumNode.variants.map((v) => {
63
+ const variantName = scope.nameApi.enumVariant(v.name as string);
64
+ return variantName;
65
+ });
66
+
67
+ const variantLines = variants.map((v: string) => ` ${v},`).join("\n");
68
+ const encoderName = scope.nameApi.encoderFunction(name);
69
+ const decoderName = scope.nameApi.decoderFunction(name);
70
+ const codecName = scope.nameApi.codecFunction(name);
71
+
72
+ // Determine size codec from the enum node's size if available
73
+ const resolvedSize = resolveNestedTypeNode(enumNode.size);
74
+ const sizeType = resolvedSize.format ?? "u8";
75
+ const sizeCodecName = getNumberCodecName(sizeType);
76
+
77
+ return fragment`// Auto-generated. Do not edit.
78
+ // ignore_for_file: type=lint
79
+
80
+ ${use("Uint8List", "dartTypedData")}
81
+ ${use("Encoder", "solanaCodecsCore")}
82
+ ${use("Decoder", "solanaCodecsCore")}
83
+ ${use("Codec", "solanaCodecsCore")}
84
+ ${use("combineCodec", "solanaCodecsCore")}
85
+ ${use("transformEncoder", "solanaCodecsCore")}
86
+ ${use("transformDecoder", "solanaCodecsCore")}
87
+ ${use(`get${sizeCodecName}Encoder`, "solanaCodecsNumbers")}
88
+ ${use(`get${sizeCodecName}Decoder`, "solanaCodecsNumbers")}
89
+
90
+ enum ${fragmentFromString(typeName)} {
91
+ ${fragmentFromString(variantLines)}
92
+ }
93
+
94
+ Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
95
+ return transformEncoder(
96
+ get${fragmentFromString(sizeCodecName)}Encoder(),
97
+ (${fragmentFromString(typeName)} value) => value.index,
98
+ );
99
+ }
100
+
101
+ Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
102
+ return transformDecoder(
103
+ get${fragmentFromString(sizeCodecName)}Decoder(),
104
+ (int value, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}.values[value],
105
+ );
106
+ }
107
+
108
+ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
109
+ return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
110
+ }`;
111
+ }
112
+
113
+ /**
114
+ * Generate a Dart data enum (sealed class with variants).
115
+ */
116
+ function getDataEnumPageFragment(
117
+ node: DefinedTypeNode,
118
+ scope: RenderScope,
119
+ ): Fragment {
120
+ const name = node.name as string;
121
+ const typeName = scope.nameApi.dataType(name);
122
+ const enumNode = node.type;
123
+ if (enumNode.kind !== "enumTypeNode") return emptyFragment();
124
+
125
+ const variantClasses: string[] = [];
126
+ const encoderVariants: string[] = [];
127
+ const decoderVariants: string[] = [];
128
+ // Collect all field type manifests from variants so we can merge their imports
129
+ const allVariantManifests: { encoder: Fragment; decoder: Fragment; type: Fragment }[] = [];
130
+
131
+ for (let i = 0; i < enumNode.variants.length; i++) {
132
+ const variant = enumNode.variants[i];
133
+ const variantClassName = scope.nameApi.sealedClassVariant(
134
+ name,
135
+ variant.name as string,
136
+ );
137
+
138
+ if (variant.kind === "enumEmptyVariantTypeNode") {
139
+ variantClasses.push(`final class ${variantClassName} extends ${typeName} {
140
+ const ${variantClassName}();
141
+
142
+ @override
143
+ bool operator ==(Object other) =>
144
+ identical(this, other) || other is ${variantClassName};
145
+
146
+ @override
147
+ int get hashCode => runtimeType.hashCode;
148
+
149
+ @override
150
+ String toString() => '${typeName}.${variantClassName}()';
151
+ }`);
152
+
153
+ encoderVariants.push(
154
+ `(${i}, getStructEncoder(<(String, Encoder<Object?>)>[]))`,
155
+ );
156
+ decoderVariants.push(
157
+ `(${i}, getStructDecoder(<(String, Decoder<Object?>)>[]).map((_) => const ${variantClassName}()))`,
158
+ );
159
+ } else if (variant.kind === "enumStructVariantTypeNode") {
160
+ const resolvedStruct = resolveNestedTypeNode(variant.struct);
161
+ const fields = resolvedStruct.fields;
162
+
163
+ // Visit each field type once and collect manifests
164
+ const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
165
+ field: f,
166
+ manifest: visit(f.type, scope.typeManifestVisitor),
167
+ }));
168
+ for (const { manifest } of fieldManifests) {
169
+ allVariantManifests.push(manifest);
170
+ }
171
+
172
+ const fieldDecls = fieldManifests
173
+ .map(({ field: f, manifest }) => {
174
+ return ` final ${manifest.type.content} ${camelCase(f.name as string)};`;
175
+ })
176
+ .join("\n");
177
+
178
+ const ctorParams = fields
179
+ .map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
180
+ .join("\n");
181
+
182
+ const eqChecks = fields
183
+ .map((f: StructFieldTypeNode) => `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`)
184
+ .join(" &&\n ");
185
+
186
+ const hashFields = fields
187
+ .map((f: StructFieldTypeNode) => camelCase(f.name as string))
188
+ .join(", ");
189
+
190
+ const toStringFields = fields
191
+ .map((f: StructFieldTypeNode) => `${camelCase(f.name as string)}: $${camelCase(f.name as string)}`)
192
+ .join(", ");
193
+
194
+ variantClasses.push(`final class ${variantClassName} extends ${typeName} {
195
+ const ${variantClassName}({
196
+ ${ctorParams}
197
+ });
198
+
199
+ ${fieldDecls}
200
+
201
+ @override
202
+ bool operator ==(Object other) =>
203
+ identical(this, other) ||
204
+ other is ${variantClassName} &&
205
+ ${eqChecks};
206
+
207
+ @override
208
+ int get hashCode => Object.hash(${hashFields});
209
+
210
+ @override
211
+ String toString() => '${typeName}.${variantClassName}(${toStringFields})';
212
+ }`);
213
+
214
+ const encFields = fieldManifests
215
+ .map(({ field: f, manifest }) => {
216
+ return `('${f.name as string}', ${manifest.encoder.content})`;
217
+ })
218
+ .join(", ");
219
+
220
+ const decFields = fieldManifests
221
+ .map(({ field: f, manifest }) => {
222
+ return `('${f.name as string}', ${manifest.decoder.content})`;
223
+ })
224
+ .join(", ");
225
+
226
+ const fromMapFields = fieldManifests
227
+ .map(
228
+ ({ field: f, manifest }) => {
229
+ const typeStr = manifest.type.content;
230
+ const isNullable = typeStr.endsWith("?");
231
+ const accessor = isNullable ? `map['${f.name as string}']` : `map['${f.name as string}']!`;
232
+ return `${camelCase(f.name as string)}: ${accessor} as ${typeStr}`;
233
+ },
234
+ )
235
+ .join(", ");
236
+
237
+ const toMapFields = fields
238
+ .map(
239
+ (f: StructFieldTypeNode) =>
240
+ `'${f.name as string}': value.${camelCase(f.name as string)}`,
241
+ )
242
+ .join(", ");
243
+
244
+ encoderVariants.push(
245
+ `(${i}, transformEncoder(getStructEncoder([${encFields}]), (${variantClassName} value) => <String, Object?>{${toMapFields}}))`,
246
+ );
247
+ decoderVariants.push(
248
+ `(${i}, transformDecoder(getStructDecoder([${decFields}]), (Map<String, Object?> map, Uint8List bytes, int offset) => ${variantClassName}(${fromMapFields})))`,
249
+ );
250
+ } else if (variant.kind === "enumTupleVariantTypeNode") {
251
+ const resolvedTuple = resolveNestedTypeNode(variant.tuple);
252
+ const items = resolvedTuple.items;
253
+ if (items.length === 1) {
254
+ const manifest = visit(items[0], scope.typeManifestVisitor);
255
+ allVariantManifests.push(manifest);
256
+ variantClasses.push(`final class ${variantClassName} extends ${typeName} {
257
+ const ${variantClassName}(this.value);
258
+
259
+ final ${manifest.type.content} value;
260
+
261
+ @override
262
+ bool operator ==(Object other) =>
263
+ identical(this, other) ||
264
+ other is ${variantClassName} && value == other.value;
265
+
266
+ @override
267
+ int get hashCode => value.hashCode;
268
+
269
+ @override
270
+ String toString() => '${typeName}.${variantClassName}(\$value)';
271
+ }`);
272
+ encoderVariants.push(
273
+ `(${i}, transformEncoder(${manifest.encoder.content}, (${variantClassName} value) => value.value))`,
274
+ );
275
+ decoderVariants.push(
276
+ `(${i}, transformDecoder(${manifest.decoder.content}, (${manifest.type.content} value, Uint8List bytes, int offset) => ${variantClassName}(value)))`,
277
+ );
278
+ }
279
+ }
280
+ }
281
+
282
+ const encoderName = scope.nameApi.encoderFunction(name);
283
+ const decoderName = scope.nameApi.decoderFunction(name);
284
+ const codecName = scope.nameApi.codecFunction(name);
285
+ const resolvedSize = resolveNestedTypeNode(enumNode.size);
286
+ const sizeType = resolvedSize.format ?? "u8";
287
+ const sizeCodecName = getNumberCodecName(sizeType);
288
+
289
+ const result = fragment`// Auto-generated. Do not edit.
290
+ // ignore_for_file: type=lint
291
+
292
+ ${use("Uint8List", "dartTypedData")}
293
+ ${use("immutable", "meta")}
294
+ ${use("Encoder", "solanaCodecsCore")}
295
+ ${use("Decoder", "solanaCodecsCore")}
296
+ ${use("Codec", "solanaCodecsCore")}
297
+ ${use("combineCodec", "solanaCodecsCore")}
298
+ ${use("transformEncoder", "solanaCodecsCore")}
299
+ ${use("transformDecoder", "solanaCodecsCore")}
300
+ ${use("getStructEncoder", "solanaCodecsDataStructures")}
301
+ ${use("getStructDecoder", "solanaCodecsDataStructures")}
302
+ ${use("getDiscriminatedUnionEncoder", "solanaCodecsDataStructures")}
303
+ ${use("getDiscriminatedUnionDecoder", "solanaCodecsDataStructures")}
304
+ ${use(`get${sizeCodecName}Encoder`, "solanaCodecsNumbers")}
305
+ ${use(`get${sizeCodecName}Decoder`, "solanaCodecsNumbers")}
306
+
307
+ sealed class ${fragmentFromString(typeName)} {
308
+ const ${fragmentFromString(typeName)}();
309
+ }
310
+
311
+ ${fragmentFromString(variantClasses.join("\n\n"))}
312
+
313
+ Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
314
+ return getDiscriminatedUnionEncoder([
315
+ ${fragmentFromString(encoderVariants.join(",\n "))},
316
+ ], size: get${fragmentFromString(sizeCodecName)}Encoder());
317
+ }
318
+
319
+ Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
320
+ return getDiscriminatedUnionDecoder([
321
+ ${fragmentFromString(decoderVariants.join(",\n "))},
322
+ ], size: get${fragmentFromString(sizeCodecName)}Decoder());
323
+ }
324
+
325
+ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
326
+ return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
327
+ }`;
328
+
329
+ // Merge variant field type manifest imports into the result
330
+ for (const manifest of allVariantManifests) {
331
+ result.imports.mergeWith(manifest.encoder.imports);
332
+ result.imports.mergeWith(manifest.decoder.imports);
333
+ result.imports.mergeWith(manifest.type.imports);
334
+ }
335
+
336
+ return result;
337
+ }
338
+
339
+ /**
340
+ * Generate a Dart @immutable class for a struct type.
341
+ */
342
+ function getStructPageFragment(
343
+ node: DefinedTypeNode,
344
+ scope: RenderScope,
345
+ ): Fragment {
346
+ const name = node.name as string;
347
+ const typeName = scope.nameApi.dataType(name);
348
+ const structNode = node.type;
349
+ if (structNode.kind !== "structTypeNode") return emptyFragment();
350
+
351
+ const fields = structNode.fields;
352
+
353
+ // Visit each field type once and collect manifests for reuse
354
+ const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
355
+ field: f,
356
+ manifest: visit(f.type, scope.typeManifestVisitor),
357
+ }));
358
+
359
+ const fieldDecls = fieldManifests
360
+ .map(({ field: f, manifest }) => {
361
+ return ` final ${manifest.type.content} ${camelCase(f.name as string)};`;
362
+ })
363
+ .join("\n");
364
+
365
+ const ctorParams = fields
366
+ .map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
367
+ .join("\n");
368
+
369
+ const eqChecks =
370
+ fields.length === 0
371
+ ? "true"
372
+ : fields
373
+ .map(
374
+ (f: StructFieldTypeNode) =>
375
+ `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`,
376
+ )
377
+ .join(" &&\n ");
378
+
379
+ const hashFields = fields
380
+ .map((f: StructFieldTypeNode) => camelCase(f.name as string))
381
+ .join(", ");
382
+
383
+ const toStringFields = fields
384
+ .map(
385
+ (f: StructFieldTypeNode) =>
386
+ `${camelCase(f.name as string)}: \$${camelCase(f.name as string)}`,
387
+ )
388
+ .join(", ");
389
+
390
+ const encoderName = scope.nameApi.encoderFunction(name);
391
+ const decoderName = scope.nameApi.decoderFunction(name);
392
+ const codecName = scope.nameApi.codecFunction(name);
393
+
394
+ // Build encoder/decoder field lists
395
+ const encFields = fieldManifests
396
+ .map(({ field: f, manifest }) => {
397
+ return ` ('${f.name as string}', ${manifest.encoder.content}),`;
398
+ })
399
+ .join("\n");
400
+
401
+ const decFields = fieldManifests
402
+ .map(({ field: f, manifest }) => {
403
+ return ` ('${f.name as string}', ${manifest.decoder.content}),`;
404
+ })
405
+ .join("\n");
406
+
407
+ const toMapFields = fields
408
+ .map(
409
+ (f: StructFieldTypeNode) =>
410
+ ` '${f.name as string}': value.${camelCase(f.name as string)},`,
411
+ )
412
+ .join("\n");
413
+
414
+ const fromMapFields = fieldManifests
415
+ .map(({ field: f, manifest }) => {
416
+ const typeStr = manifest.type.content;
417
+ const isNullable = typeStr.endsWith("?");
418
+ const accessor = isNullable ? `map['${f.name as string}']` : `map['${f.name as string}']!`;
419
+ return ` ${camelCase(f.name as string)}: ${accessor} as ${typeStr},`;
420
+ })
421
+ .join("\n");
422
+
423
+ const result = fragment`// Auto-generated. Do not edit.
424
+ // ignore_for_file: type=lint
425
+
426
+ ${use("Uint8List", "dartTypedData")}
427
+ ${use("immutable", "meta")}
428
+ ${use("Encoder", "solanaCodecsCore")}
429
+ ${use("Decoder", "solanaCodecsCore")}
430
+ ${use("Codec", "solanaCodecsCore")}
431
+ ${use("combineCodec", "solanaCodecsCore")}
432
+ ${use("transformEncoder", "solanaCodecsCore")}
433
+ ${use("transformDecoder", "solanaCodecsCore")}
434
+ ${use("getStructEncoder", "solanaCodecsDataStructures")}
435
+ ${use("getStructDecoder", "solanaCodecsDataStructures")}
436
+
437
+ @immutable
438
+ class ${fragmentFromString(typeName)} {
439
+ const ${fragmentFromString(typeName)}({
440
+ ${fragmentFromString(ctorParams)}
441
+ });
442
+
443
+ ${fragmentFromString(fieldDecls)}
444
+
445
+ @override
446
+ bool operator ==(Object other) =>
447
+ identical(this, other) ||
448
+ other is ${fragmentFromString(typeName)} &&
449
+ runtimeType == other.runtimeType &&
450
+ ${fragmentFromString(eqChecks)};
451
+
452
+ @override
453
+ int get hashCode => Object.hash(${fragmentFromString(hashFields)});
454
+
455
+ @override
456
+ String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
457
+ }
458
+
459
+ Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
460
+ final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
461
+ ${fragmentFromString(encFields)}
462
+ ]);
463
+
464
+ return transformEncoder(
465
+ structEncoder,
466
+ (${fragmentFromString(typeName)} value) => <String, Object?>{
467
+ ${fragmentFromString(toMapFields)}
468
+ },
469
+ );
470
+ }
471
+
472
+ Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
473
+ final structDecoder = getStructDecoder(<(String, Decoder<Object?>)>[
474
+ ${fragmentFromString(decFields)}
475
+ ]);
476
+
477
+ return transformDecoder(
478
+ structDecoder,
479
+ (Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}(
480
+ ${fragmentFromString(fromMapFields)}
481
+ ),
482
+ );
483
+ }
484
+
485
+ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
486
+ return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
487
+ }`;
488
+
489
+ // Merge field type manifest imports (encoder, decoder, type) into the result
490
+ for (const { manifest } of fieldManifests) {
491
+ result.imports.mergeWith(manifest.encoder.imports);
492
+ result.imports.mergeWith(manifest.decoder.imports);
493
+ result.imports.mergeWith(manifest.type.imports);
494
+ }
495
+
496
+ return result;
497
+ }
498
+
499
+ /**
500
+ * Generate a simple type alias with codec wrappers.
501
+ */
502
+ function getTypeAliasPageFragment(
503
+ node: DefinedTypeNode,
504
+ scope: RenderScope,
505
+ ): Fragment {
506
+ const name = node.name as string;
507
+ const typeName = scope.nameApi.dataType(name);
508
+ const manifest = visit(node.type, scope.typeManifestVisitor);
509
+
510
+ const encoderName = scope.nameApi.encoderFunction(name);
511
+ const decoderName = scope.nameApi.decoderFunction(name);
512
+ const codecName = scope.nameApi.codecFunction(name);
513
+
514
+ return fragment`// Auto-generated. Do not edit.
515
+ // ignore_for_file: type=lint
516
+
517
+ ${use("Encoder", "solanaCodecsCore")}
518
+ ${use("Decoder", "solanaCodecsCore")}
519
+ ${use("Codec", "solanaCodecsCore")}
520
+ ${use("combineCodec", "solanaCodecsCore")}
521
+
522
+ typedef ${fragmentFromString(typeName)} = ${manifest.type};
523
+
524
+ Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
525
+ return ${manifest.encoder};
526
+ }
527
+
528
+ Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
529
+ return ${manifest.decoder};
530
+ }
531
+
532
+ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
533
+ return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
534
+ }`;
535
+ }
536
+
537
+ function getNumberCodecName(format: string): string {
538
+ switch (format) {
539
+ case "u8": return "U8";
540
+ case "u16": return "U16";
541
+ case "u32": return "U32";
542
+ case "u64": return "U64";
543
+ case "u128": return "U128";
544
+ case "i8": return "I8";
545
+ case "i16": return "I16";
546
+ case "i32": return "I32";
547
+ case "i64": return "I64";
548
+ case "i128": return "I128";
549
+ case "f32": return "F32";
550
+ case "f64": return "F64";
551
+ case "shortU16": return "ShortU16";
552
+ default: return "U8";
553
+ }
554
+ }
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export { renderVisitor } from "./visitors/renderVisitor.js";
2
+ export { getRenderMapVisitor } from "./visitors/getRenderMapVisitor.js";
3
+ export { getTypeManifestVisitor } from "./visitors/getTypeManifestVisitor.js";
4
+ export { DartImportMap, DART_EXTERNAL_PACKAGE_MAP } from "./utils/importMap.js";
5
+ export { createDartNameApi } from "./utils/nameTransformers.js";
6
+ export type { DartNameApi } from "./utils/nameTransformers.js";
7
+ export type { RenderOptions, GetRenderMapOptions, RenderScope } from "./utils/options.js";
8
+ export type { TypeManifest } from "./utils/typeManifest.js";
9
+ export type { Fragment } from "./utils/fragment.js";
10
+
11
+ // Default export for Codama CLI integration
12
+ import { renderVisitor } from "./visitors/renderVisitor.js";
13
+ export default renderVisitor;
@@ -0,0 +1,2 @@
1
+ declare const __NODEJS__: boolean;
2
+ declare const __VERSION__: string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Convert a Uint8Array to a Dart hex literal string.
3
+ * Used for discriminator constants.
4
+ */
5
+ export function bytesToDartHexList(bytes: Uint8Array): string {
6
+ const pairs: string[] = [];
7
+ for (let i = 0; i < bytes.length; i++) {
8
+ pairs.push(`0x${bytes[i].toString(16).padStart(2, "0")}`);
9
+ }
10
+ return `[${pairs.join(", ")}]`;
11
+ }
12
+
13
+ /**
14
+ * Convert a Uint8Array to a Dart Uint8List.fromList() expression.
15
+ */
16
+ export function bytesToDartUint8List(bytes: Uint8Array): string {
17
+ const hex = bytesToDartHexList(bytes);
18
+ return `Uint8List.fromList(${hex})`;
19
+ }
@@ -0,0 +1,35 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ /**
4
+ * Format Dart code using `dart format`.
5
+ * Falls back to returning the unformatted code if dart is not available.
6
+ */
7
+ export function formatDartCode(code: string): string {
8
+ try {
9
+ const result = execSync("dart format --fix -", {
10
+ input: code,
11
+ encoding: "utf-8",
12
+ timeout: 30_000,
13
+ stdio: ["pipe", "pipe", "pipe"],
14
+ });
15
+ return result;
16
+ } catch {
17
+ // If dart format is not available, return as-is
18
+ return code;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Format all Dart files in a directory using `dart format`.
24
+ */
25
+ export function formatDartDirectory(dir: string): void {
26
+ try {
27
+ execSync(`dart format --fix "${dir}"`, {
28
+ encoding: "utf-8",
29
+ timeout: 60_000,
30
+ stdio: ["pipe", "pipe", "pipe"],
31
+ });
32
+ } catch {
33
+ // Silently ignore if dart format is not available
34
+ }
35
+ }