codama-renderers-dart 0.5.1 → 0.5.3
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 → README.md} +10 -43
- package/dist/index.browser.cjs +173 -21
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.js +173 -21
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.cjs +173 -21
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.js +173 -21
- package/dist/index.node.js.map +1 -1
- package/dist/index.react-native.js +173 -21
- package/dist/index.react-native.js.map +1 -1
- package/dist/types/fragments/instructionPage.d.ts.map +1 -1
- package/dist/types/utils/nameTransformers.d.ts +5 -0
- package/dist/types/utils/nameTransformers.d.ts.map +1 -1
- package/dist/types/visitors/getTypeManifestVisitor.d.ts +1 -1
- package/dist/types/visitors/getTypeManifestVisitor.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/fragments/instructionPage.ts +9 -2
- package/src/fragments/typePage.ts +102 -19
- package/src/utils/nameTransformers.ts +28 -1
- package/src/visitors/getTypeManifestVisitor.ts +24 -7
package/{readme.md → README.md}
RENAMED
|
@@ -13,18 +13,14 @@ Fixed-size Codama types generate non-truncating encoders. Values within the decl
|
|
|
13
13
|
```bash
|
|
14
14
|
pnpm add codama-renderers-dart
|
|
15
15
|
# or
|
|
16
|
-
|
|
17
|
-
[](https://pub.dev/packages/codama-renderers-dart)
|
|
18
|
-
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
19
|
-
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
20
16
|
npm install codama-renderers-dart
|
|
21
17
|
```
|
|
22
18
|
|
|
23
|
-
## Quick
|
|
19
|
+
## Quick start
|
|
24
20
|
|
|
25
21
|
### Programmatic API
|
|
26
22
|
|
|
27
|
-
```
|
|
23
|
+
```ts
|
|
28
24
|
import { renderVisitor } from "codama-renderers-dart";
|
|
29
25
|
import { visit } from "@codama/visitors-core";
|
|
30
26
|
import { rootNode, programNode /* ... */ } from "@codama/nodes";
|
|
@@ -43,7 +39,7 @@ visit(root, renderVisitor("lib/src/generated", {
|
|
|
43
39
|
|
|
44
40
|
`renderVisitor` accepts parsed Codama JSON as well as roots made with Codama constructors. Pina omits empty collections from its serialized IDLs; the renderer restores those structural defaults on an internal copy before traversal, leaving the parsed object unchanged.
|
|
45
41
|
|
|
46
|
-
```
|
|
42
|
+
```ts
|
|
47
43
|
import { readFile } from "node:fs/promises";
|
|
48
44
|
import { visit } from "@codama/visitors-core";
|
|
49
45
|
import { renderVisitor } from "codama-renderers-dart";
|
|
@@ -76,7 +72,7 @@ Then run:
|
|
|
76
72
|
codama run dart
|
|
77
73
|
```
|
|
78
74
|
|
|
79
|
-
## Generated
|
|
75
|
+
## Generated output structure
|
|
80
76
|
|
|
81
77
|
For a program called `myProgram`, the renderer generates:
|
|
82
78
|
|
|
@@ -104,7 +100,7 @@ lib/src/generated/
|
|
|
104
100
|
my_pda.dart # PDA seeds class + finder function
|
|
105
101
|
```
|
|
106
102
|
|
|
107
|
-
## Generated
|
|
103
|
+
## Generated code patterns
|
|
108
104
|
|
|
109
105
|
### Accounts
|
|
110
106
|
|
|
@@ -166,7 +162,7 @@ Top-level instruction decoders require exact input consumption. Account decoders
|
|
|
166
162
|
|
|
167
163
|
Optional instruction accounts use Codama's `programId` strategy by default. When an optional account is absent, the builder emits a readonly program-address placeholder so every later account keeps its declared index. The account slot is removed only when the instruction explicitly selects the legacy `omitted` strategy.
|
|
168
164
|
|
|
169
|
-
### Scalar
|
|
165
|
+
### Scalar enums
|
|
170
166
|
|
|
171
167
|
Scalar enums (all-empty variants) generate a Dart `enum` with index-based encoder/decoder:
|
|
172
168
|
|
|
@@ -183,7 +179,7 @@ Decoder<AccountStatus> getAccountStatusDecoder() { ... }
|
|
|
183
179
|
|
|
184
180
|
The generated enum API is the same for `u8`, `u16`, `u32`, and `u64` discriminators. The renderer converts `u64` indices to and from `BigInt` at the codec boundary and rejects out-of-range discriminators before converting them to Dart enum indices.
|
|
185
181
|
|
|
186
|
-
### Data
|
|
182
|
+
### Data enums (discriminated unions)
|
|
187
183
|
|
|
188
184
|
Data enums generate Dart 3 `sealed class` hierarchies:
|
|
189
185
|
|
|
@@ -231,7 +227,7 @@ Future<(Address, int)> findMyPdaPda({
|
|
|
231
227
|
}) async { ... }
|
|
232
228
|
```
|
|
233
229
|
|
|
234
|
-
## Type
|
|
230
|
+
## Type mapping
|
|
235
231
|
|
|
236
232
|
| Codama Type | Dart Type | Codec |
|
|
237
233
|
| ----------------------------------- | --------------- | -------------------------------- |
|
|
@@ -266,7 +262,7 @@ Future<(Address, int)> findMyPdaPda({
|
|
|
266
262
|
|
|
267
263
|
All naming conventions are customizable:
|
|
268
264
|
|
|
269
|
-
```
|
|
265
|
+
```ts
|
|
270
266
|
import { renderVisitor, createDartNameApi } from "codama-renderers-dart";
|
|
271
267
|
|
|
272
268
|
const nameApi = {
|
|
@@ -277,7 +273,7 @@ const nameApi = {
|
|
|
277
273
|
visit(root, renderVisitor("output", { nameApi }));
|
|
278
274
|
```
|
|
279
275
|
|
|
280
|
-
## Target
|
|
276
|
+
## Target packages
|
|
281
277
|
|
|
282
278
|
Generated Dart code depends on these solana_kit packages:
|
|
283
279
|
|
|
@@ -311,39 +307,10 @@ Key source files:
|
|
|
311
307
|
## Development
|
|
312
308
|
|
|
313
309
|
```bash
|
|
314
|
-
# Install dependencies
|
|
315
|
-
|
|
316
|
-
[](https://pub.dev/packages/codama-renderers-dart)
|
|
317
|
-
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
318
|
-
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
319
310
|
pnpm install
|
|
320
|
-
|
|
321
|
-
# Type check
|
|
322
|
-
|
|
323
|
-
[](https://pub.dev/packages/codama-renderers-dart)
|
|
324
|
-
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
325
|
-
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
326
311
|
npx tsc --noEmit
|
|
327
|
-
|
|
328
|
-
# Run tests
|
|
329
|
-
|
|
330
|
-
[](https://pub.dev/packages/codama-renderers-dart)
|
|
331
|
-
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
332
|
-
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
333
312
|
pnpm test
|
|
334
|
-
|
|
335
|
-
# Watch mode
|
|
336
|
-
|
|
337
|
-
[](https://pub.dev/packages/codama-renderers-dart)
|
|
338
|
-
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
339
|
-
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
340
313
|
pnpm test:watch
|
|
341
|
-
|
|
342
|
-
# Build
|
|
343
|
-
|
|
344
|
-
[](https://pub.dev/packages/codama-renderers-dart)
|
|
345
|
-
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
346
|
-
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
347
314
|
pnpm build
|
|
348
315
|
```
|
|
349
316
|
|
package/dist/index.browser.cjs
CHANGED
|
@@ -84,9 +84,83 @@ function setEmptyCollection(record, key) {
|
|
|
84
84
|
function pascalCase(str) {
|
|
85
85
|
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase());
|
|
86
86
|
}
|
|
87
|
+
var DART_RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
88
|
+
"abstract",
|
|
89
|
+
"as",
|
|
90
|
+
"assert",
|
|
91
|
+
"async",
|
|
92
|
+
"augment",
|
|
93
|
+
"await",
|
|
94
|
+
"base",
|
|
95
|
+
"break",
|
|
96
|
+
"case",
|
|
97
|
+
"catch",
|
|
98
|
+
"class",
|
|
99
|
+
"const",
|
|
100
|
+
"continue",
|
|
101
|
+
"covariant",
|
|
102
|
+
"default",
|
|
103
|
+
"deferred",
|
|
104
|
+
"do",
|
|
105
|
+
"dynamic",
|
|
106
|
+
"else",
|
|
107
|
+
"enum",
|
|
108
|
+
"export",
|
|
109
|
+
"extends",
|
|
110
|
+
"extension",
|
|
111
|
+
"external",
|
|
112
|
+
"factory",
|
|
113
|
+
"false",
|
|
114
|
+
"final",
|
|
115
|
+
"finally",
|
|
116
|
+
"for",
|
|
117
|
+
"get",
|
|
118
|
+
"hide",
|
|
119
|
+
"if",
|
|
120
|
+
"implements",
|
|
121
|
+
"import",
|
|
122
|
+
"in",
|
|
123
|
+
"interface",
|
|
124
|
+
"is",
|
|
125
|
+
"late",
|
|
126
|
+
"library",
|
|
127
|
+
"mixin",
|
|
128
|
+
"native",
|
|
129
|
+
"new",
|
|
130
|
+
"null",
|
|
131
|
+
"of",
|
|
132
|
+
"on",
|
|
133
|
+
"operator",
|
|
134
|
+
"part",
|
|
135
|
+
"required",
|
|
136
|
+
"rethrow",
|
|
137
|
+
"return",
|
|
138
|
+
"sealed",
|
|
139
|
+
"set",
|
|
140
|
+
"show",
|
|
141
|
+
"static",
|
|
142
|
+
"super",
|
|
143
|
+
"switch",
|
|
144
|
+
"sync",
|
|
145
|
+
"this",
|
|
146
|
+
"throw",
|
|
147
|
+
"true",
|
|
148
|
+
"try",
|
|
149
|
+
"typedef",
|
|
150
|
+
"var",
|
|
151
|
+
"void",
|
|
152
|
+
"when",
|
|
153
|
+
"while",
|
|
154
|
+
"with",
|
|
155
|
+
"yield"
|
|
156
|
+
]);
|
|
157
|
+
function escapeDartIdentifier(name) {
|
|
158
|
+
return DART_RESERVED_WORDS.has(name) ? `${name}_` : name;
|
|
159
|
+
}
|
|
87
160
|
function camelCase(str) {
|
|
88
161
|
const pascal = pascalCase(str);
|
|
89
|
-
|
|
162
|
+
const cased = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
163
|
+
return escapeDartIdentifier(cased);
|
|
90
164
|
}
|
|
91
165
|
function snakeCase(str) {
|
|
92
166
|
return str.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").replace(/^_/, "").replace(/_+/g, "_").toLowerCase();
|
|
@@ -674,7 +748,7 @@ function getTypeManifestVisitor(input) {
|
|
|
674
748
|
encoder: fragment`${use(
|
|
675
749
|
"getArrayEncoder",
|
|
676
750
|
"solanaCodecsDataStructures"
|
|
677
|
-
)}
|
|
751
|
+
)}(${itemEncoder}${encoderSizeExpr})`,
|
|
678
752
|
decoder: fragment`${use(
|
|
679
753
|
"getArrayDecoder",
|
|
680
754
|
"solanaCodecsDataStructures"
|
|
@@ -730,18 +804,28 @@ function getTypeManifestVisitor(input) {
|
|
|
730
804
|
typeFragment.imports.mergeWith(m.type.imports);
|
|
731
805
|
}
|
|
732
806
|
const encoderListStr = encoderList.map((e) => e.content).join(", ");
|
|
807
|
+
const decoderListStr = decoderList.map((d) => d.content).join(", ");
|
|
808
|
+
let tupleEncoderFn = "getTupleEncoder";
|
|
809
|
+
let tupleDecoderFn = "getTupleDecoder";
|
|
810
|
+
let tupleEncoderArgs = `[${encoderListStr}]`;
|
|
811
|
+
let tupleDecoderArgs = `[${decoderListStr}]`;
|
|
812
|
+
if (itemManifests.length === 2) {
|
|
813
|
+
tupleEncoderFn = "getTuple2Encoder";
|
|
814
|
+
tupleDecoderFn = "getTuple2Decoder";
|
|
815
|
+
tupleEncoderArgs = encoderListStr;
|
|
816
|
+
tupleDecoderArgs = decoderListStr;
|
|
817
|
+
}
|
|
733
818
|
const encoderFrag = fragment`${use(
|
|
734
|
-
|
|
819
|
+
tupleEncoderFn,
|
|
735
820
|
"solanaCodecsDataStructures"
|
|
736
|
-
)}(
|
|
821
|
+
)}(${tupleEncoderArgs})`;
|
|
737
822
|
for (const e of encoderList) {
|
|
738
823
|
encoderFrag.imports.mergeWith(e.imports);
|
|
739
824
|
}
|
|
740
|
-
const decoderListStr = decoderList.map((d) => d.content).join(", ");
|
|
741
825
|
const decoderFrag = fragment`${use(
|
|
742
|
-
|
|
826
|
+
tupleDecoderFn,
|
|
743
827
|
"solanaCodecsDataStructures"
|
|
744
|
-
)}(
|
|
828
|
+
)}(${tupleDecoderArgs})`;
|
|
745
829
|
for (const d of decoderList) {
|
|
746
830
|
decoderFrag.imports.mergeWith(d.imports);
|
|
747
831
|
}
|
|
@@ -828,7 +912,10 @@ function getTypeManifestVisitor(input) {
|
|
|
828
912
|
encoder: fragment`${use(
|
|
829
913
|
"getNullableEncoder",
|
|
830
914
|
"solanaCodecsDataStructures"
|
|
831
|
-
)}<${fragmentFromString(itemTypeStr)}>(${
|
|
915
|
+
)}<${fragmentFromString(itemTypeStr)}>(${use(
|
|
916
|
+
"transformEncoder",
|
|
917
|
+
"solanaCodecsCore"
|
|
918
|
+
)}(${itemManifest.encoder}, (${fragmentFromString(itemTypeStr)} value) => value)${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
|
|
832
919
|
decoder: fragment`${use(
|
|
833
920
|
"getNullableDecoder",
|
|
834
921
|
"solanaCodecsDataStructures"
|
|
@@ -1589,6 +1676,10 @@ ${dataCtorInitializers}` : ""};`;
|
|
|
1589
1676
|
const accountNames = new Set(accounts.map((a) => camelCase(a.name)));
|
|
1590
1677
|
const hasProgramAddressCollision = argNames.has("programAddress") || accountNames.has("programAddress");
|
|
1591
1678
|
const instrProgramParam = hasProgramAddressCollision ? "instructionProgramAddress" : "programAddress";
|
|
1679
|
+
let instructionDataLocal = "instructionData";
|
|
1680
|
+
while (argNames.has(instructionDataLocal) || accountNames.has(instructionDataLocal)) {
|
|
1681
|
+
instructionDataLocal = `${instructionDataLocal}_`;
|
|
1682
|
+
}
|
|
1592
1683
|
const accountParams = accounts.map((acc) => {
|
|
1593
1684
|
const fieldName = camelCase(acc.name);
|
|
1594
1685
|
const isRequired = !(acc.isOptional ?? false);
|
|
@@ -1697,7 +1788,7 @@ Instruction ${fragmentFromString(instrFnName)}({
|
|
|
1697
1788
|
${fragmentFromString(accountParams)}
|
|
1698
1789
|
${fragmentFromString(argParams)}
|
|
1699
1790
|
}) {
|
|
1700
|
-
final
|
|
1791
|
+
final ${instructionDataLocal} = ${fragmentFromString(dataClassName)}(
|
|
1701
1792
|
${fragmentFromString(dataConstruction)}
|
|
1702
1793
|
);
|
|
1703
1794
|
|
|
@@ -1706,7 +1797,7 @@ ${fragmentFromString(dataConstruction)}
|
|
|
1706
1797
|
accounts: [
|
|
1707
1798
|
${fragmentFromString(accountMetas)}
|
|
1708
1799
|
],
|
|
1709
|
-
data: ${fragmentFromString(dataEncoderName)}().encode(
|
|
1800
|
+
data: ${fragmentFromString(dataEncoderName)}().encode(${instructionDataLocal}),
|
|
1710
1801
|
);
|
|
1711
1802
|
}`);
|
|
1712
1803
|
parts.push(fragment`
|
|
@@ -2130,6 +2221,7 @@ function getDataEnumPageFragment(node, variants, scope) {
|
|
|
2130
2221
|
const decoderVariants = [];
|
|
2131
2222
|
const encodeCases = [];
|
|
2132
2223
|
const decodeCases = [];
|
|
2224
|
+
let pageHasListEnums = false;
|
|
2133
2225
|
const allVariantManifests = [];
|
|
2134
2226
|
for (let i = 0; i < variants.length; i++) {
|
|
2135
2227
|
const variant = variants[i];
|
|
@@ -2177,10 +2269,14 @@ function getDataEnumPageFragment(node, variants, scope) {
|
|
|
2177
2269
|
const ctorSignature = fields.length === 0 ? ` const ${variantClassName}();` : ` const ${variantClassName}({
|
|
2178
2270
|
${ctorParams}
|
|
2179
2271
|
});`;
|
|
2180
|
-
const eqChecks = fields.length === 0 ? "true" : fields.map((f) =>
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
);
|
|
2272
|
+
const eqChecks = fields.length === 0 ? "true" : fields.map((f, index) => {
|
|
2273
|
+
const fieldName = camelCase(f.name);
|
|
2274
|
+
return isListManifest(fieldManifests[index].manifest) ? `_listEquals(${fieldName}, other.${fieldName})` : `${fieldName} == other.${fieldName}`;
|
|
2275
|
+
}).join(" &&\n ");
|
|
2276
|
+
const hashExpression = getValueHashExpression(fields, fieldManifests);
|
|
2277
|
+
if (fieldManifests.some(({ manifest }) => isListManifest(manifest))) {
|
|
2278
|
+
pageHasListEnums = true;
|
|
2279
|
+
}
|
|
2184
2280
|
const toStringFields = fields.map((f) => `${camelCase(f.name)}: $${camelCase(f.name)}`).join(", ");
|
|
2185
2281
|
variantClasses.push(`final class ${variantClassName} extends ${typeName} {
|
|
2186
2282
|
${ctorSignature}
|
|
@@ -2286,7 +2382,7 @@ sealed class ${fragmentFromString(typeName)} {
|
|
|
2286
2382
|
const ${fragmentFromString(typeName)}();
|
|
2287
2383
|
}
|
|
2288
2384
|
|
|
2289
|
-
${fragmentFromString(variantClasses.join("\n\n"))}
|
|
2385
|
+
${fragmentFromString(variantClasses.join("\n\n"))}${pageHasListEnums ? LIST_VALUE_HELPERS : ""}
|
|
2290
2386
|
|
|
2291
2387
|
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
2292
2388
|
return transformEncoder<Map<String, Object?>, ${fragmentFromString(typeName)}>(
|
|
@@ -2329,6 +2425,7 @@ function getStructPageFragment(node, scope) {
|
|
|
2329
2425
|
const structNode = node.type;
|
|
2330
2426
|
if (structNode.kind !== "structTypeNode") return emptyFragment();
|
|
2331
2427
|
const fields = structNode.fields ?? [];
|
|
2428
|
+
let pageHasListStructs = false;
|
|
2332
2429
|
const fieldManifests = fields.map((f) => ({
|
|
2333
2430
|
field: f,
|
|
2334
2431
|
manifest: visitorsCore.visit(f.type, scope.typeManifestVisitor)
|
|
@@ -2340,10 +2437,13 @@ function getStructPageFragment(node, scope) {
|
|
|
2340
2437
|
const ctorSignature = fields.length === 0 ? ` const ${typeName}();` : ` const ${typeName}({
|
|
2341
2438
|
${ctorParams}
|
|
2342
2439
|
});`;
|
|
2343
|
-
const eqChecks = fields.length === 0 ? "true" :
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2440
|
+
const eqChecks = fields.length === 0 ? "true" : fieldManifests.map(({ field: f, manifest }, index) => {
|
|
2441
|
+
const fieldName = camelCase(f.name);
|
|
2442
|
+
return isListManifest(manifest) ? `_listEquals(${fieldName}, other.${fieldName})` : `${fieldName} == other.${fieldName}`;
|
|
2443
|
+
}).join(" &&\n ");
|
|
2444
|
+
if (fieldManifests.some(({ manifest }) => isListManifest(manifest))) {
|
|
2445
|
+
pageHasListStructs = true;
|
|
2446
|
+
}
|
|
2347
2447
|
const toStringFields = fields.map(
|
|
2348
2448
|
(f) => `${camelCase(f.name)}: $${camelCase(f.name)}`
|
|
2349
2449
|
).join(", ");
|
|
@@ -2393,12 +2493,14 @@ ${fragmentFromString(fieldDecls)}
|
|
|
2393
2493
|
${fragmentFromString(eqChecks)};
|
|
2394
2494
|
|
|
2395
2495
|
@override
|
|
2396
|
-
int get hashCode => ${fragmentFromString(
|
|
2496
|
+
int get hashCode => ${fragmentFromString(
|
|
2497
|
+
getValueHashExpression(fields, fieldManifests)
|
|
2498
|
+
)};
|
|
2397
2499
|
|
|
2398
2500
|
@override
|
|
2399
2501
|
String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
|
|
2400
2502
|
}
|
|
2401
|
-
|
|
2503
|
+
${pageHasListStructs ? LIST_VALUE_HELPERS : ""}
|
|
2402
2504
|
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
2403
2505
|
final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
|
|
2404
2506
|
${fragmentFromString(encFields)}
|
|
@@ -2464,6 +2566,56 @@ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragme
|
|
|
2464
2566
|
return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
|
|
2465
2567
|
}`;
|
|
2466
2568
|
}
|
|
2569
|
+
function isListManifest(manifest) {
|
|
2570
|
+
return manifest.type.content.startsWith("List<") || manifest.type.content === "Uint8List";
|
|
2571
|
+
}
|
|
2572
|
+
var LIST_VALUE_HELPERS = `
|
|
2573
|
+
bool _listEquals<T>(List<T>? a, List<T>? b) {
|
|
2574
|
+
if (identical(a, b)) return true;
|
|
2575
|
+
if (a == null || b == null) return a == b;
|
|
2576
|
+
if (a.length != b.length) return false;
|
|
2577
|
+
for (var i = 0; i < a.length; i++) {
|
|
2578
|
+
final left = a[i];
|
|
2579
|
+
final right = b[i];
|
|
2580
|
+
if (left is List<Object?> && right is List<Object?>) {
|
|
2581
|
+
if (!_listEquals(left, right)) return false;
|
|
2582
|
+
} else if (left != right) {
|
|
2583
|
+
return false;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
return true;
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
Object? _deepHash(Object? value) {
|
|
2590
|
+
if (value is List<Object?>) {
|
|
2591
|
+
return Object.hashAll(value.map(_deepHash));
|
|
2592
|
+
}
|
|
2593
|
+
return value;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
int _listHashCode<T>(List<T>? a) {
|
|
2597
|
+
if (a == null) return 0;
|
|
2598
|
+
return Object.hashAll(a.map(_deepHash));
|
|
2599
|
+
}
|
|
2600
|
+
`;
|
|
2601
|
+
function getValueHashExpression(fields, fieldManifests) {
|
|
2602
|
+
const fieldNames = fields.map(
|
|
2603
|
+
(f) => camelCase(f.name)
|
|
2604
|
+
);
|
|
2605
|
+
const hasListFields = fieldManifests.some(
|
|
2606
|
+
({ manifest }) => isListManifest(manifest)
|
|
2607
|
+
);
|
|
2608
|
+
if (!hasListFields) {
|
|
2609
|
+
return getHashExpression(fieldNames);
|
|
2610
|
+
}
|
|
2611
|
+
if (fieldNames.length === 1) {
|
|
2612
|
+
return `_listHashCode(${fieldNames[0]})`;
|
|
2613
|
+
}
|
|
2614
|
+
const hashArgs = fields.map(
|
|
2615
|
+
(f, index) => isListManifest(fieldManifests[index].manifest) ? `_listHashCode(${camelCase(f.name)})` : camelCase(f.name)
|
|
2616
|
+
);
|
|
2617
|
+
return `Object.hash(${hashArgs.join(", ")})`;
|
|
2618
|
+
}
|
|
2467
2619
|
function getHashExpression(fields) {
|
|
2468
2620
|
if (fields.length === 0) return "runtimeType.hashCode";
|
|
2469
2621
|
if (fields.length === 1) return `${fields[0]}.hashCode`;
|