@macroforge/mcp-server 0.1.37 → 0.1.39
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/docs/api/api-overview.md +13 -13
- package/docs/api/expand-sync.md +8 -8
- package/docs/api/native-plugin.md +15 -15
- package/docs/api/position-mapper.md +6 -6
- package/docs/api/transform-sync.md +11 -11
- package/docs/builtin-macros/clone.md +43 -23
- package/docs/builtin-macros/debug.md +50 -18
- package/docs/builtin-macros/default.md +79 -28
- package/docs/builtin-macros/deserialize/cycleforward-reference-support.md +11 -0
- package/docs/builtin-macros/deserialize/example.md +1625 -0
- package/docs/builtin-macros/deserialize/overview.md +15 -10
- package/docs/builtin-macros/deserialize/union-type-deserialization.md +27 -0
- package/docs/builtin-macros/deserialize/validation.md +34 -0
- package/docs/builtin-macros/deserialize.md +1608 -23
- package/docs/builtin-macros/hash.md +87 -20
- package/docs/builtin-macros/macros-overview.md +40 -40
- package/docs/builtin-macros/ord.md +56 -31
- package/docs/builtin-macros/partial-eq/example.md +526 -0
- package/docs/builtin-macros/partial-eq/overview.md +39 -0
- package/docs/builtin-macros/partial-eq.md +184 -26
- package/docs/builtin-macros/partial-ord.md +68 -30
- package/docs/builtin-macros/serialize/example.md +139 -0
- package/docs/builtin-macros/serialize/overview.md +32 -0
- package/docs/builtin-macros/serialize/type-specific-serialization.md +22 -0
- package/docs/builtin-macros/serialize.md +130 -28
- package/docs/concepts/architecture.md +2 -2
- package/docs/concepts/derive-system.md +25 -39
- package/docs/concepts/how-macros-work.md +8 -4
- package/docs/custom-macros/custom-overview.md +23 -23
- package/docs/custom-macros/rust-setup.md +31 -31
- package/docs/custom-macros/ts-macro-derive.md +107 -107
- package/docs/custom-macros/ts-quote.md +226 -226
- package/docs/getting-started/first-macro.md +38 -28
- package/docs/getting-started/installation.md +15 -15
- package/docs/integration/cli.md +9 -9
- package/docs/integration/configuration.md +16 -16
- package/docs/integration/mcp-server.md +6 -6
- package/docs/integration/svelte-preprocessor.md +40 -41
- package/docs/integration/typescript-plugin.md +13 -12
- package/docs/integration/vite-plugin.md +12 -12
- package/docs/language-servers/zed.md +1 -1
- package/docs/sections.json +88 -2
- package/package.json +2 -2
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
## Example
|
|
2
|
+
|
|
3
|
+
```typescript before
|
|
4
|
+
/** @derive(PartialEq, Hash) */
|
|
5
|
+
class User {
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
|
|
9
|
+
@partialEq(skip) // Don't compare cached values
|
|
10
|
+
/** @hash({ skip: true }) */
|
|
11
|
+
cachedScore: number;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```typescript after
|
|
16
|
+
class User {
|
|
17
|
+
id: number;
|
|
18
|
+
name: string;
|
|
19
|
+
|
|
20
|
+
// Don't compare cached values
|
|
21
|
+
/** @hash({ skip: true }) */
|
|
22
|
+
cachedScore: number;
|
|
23
|
+
|
|
24
|
+
equals(other: unknown): boolean {
|
|
25
|
+
if (this === other) return true;
|
|
26
|
+
if (!(other instanceof User)) return false;
|
|
27
|
+
const typedOther = other as User;
|
|
28
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
hashCode(): number {
|
|
32
|
+
let hash = 17;
|
|
33
|
+
hash =
|
|
34
|
+
(hash * 31 +
|
|
35
|
+
(Number.isInteger(this.id)
|
|
36
|
+
? this.id | 0
|
|
37
|
+
: this.id
|
|
38
|
+
.toString()
|
|
39
|
+
.split('')
|
|
40
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
41
|
+
0;
|
|
42
|
+
hash =
|
|
43
|
+
(hash * 31 +
|
|
44
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
45
|
+
0;
|
|
46
|
+
hash =
|
|
47
|
+
(hash * 31 +
|
|
48
|
+
(Number.isInteger(this.cachedScore)
|
|
49
|
+
? this.cachedScore | 0
|
|
50
|
+
: this.cachedScore
|
|
51
|
+
.toString()
|
|
52
|
+
.split('')
|
|
53
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
54
|
+
0;
|
|
55
|
+
return hash;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Generated output:
|
|
61
|
+
|
|
62
|
+
```typescript before
|
|
63
|
+
class User {
|
|
64
|
+
id: number;
|
|
65
|
+
name: string;
|
|
66
|
+
|
|
67
|
+
// Don't compare cached values
|
|
68
|
+
/** @hash({ skip: true }) */
|
|
69
|
+
cachedScore: number;
|
|
70
|
+
|
|
71
|
+
equals(other: unknown): boolean {
|
|
72
|
+
if (this === other) return true;
|
|
73
|
+
if (!(other instanceof User)) return false;
|
|
74
|
+
const typedOther = other as User;
|
|
75
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
hashCode(): number {
|
|
79
|
+
let hash = 17;
|
|
80
|
+
hash =
|
|
81
|
+
(hash * 31 +
|
|
82
|
+
(Number.isInteger(this.id)
|
|
83
|
+
? this.id | 0
|
|
84
|
+
: this.id
|
|
85
|
+
.toString()
|
|
86
|
+
.split('')
|
|
87
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
88
|
+
0;
|
|
89
|
+
hash =
|
|
90
|
+
(hash * 31 +
|
|
91
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
92
|
+
0;
|
|
93
|
+
hash =
|
|
94
|
+
(hash * 31 +
|
|
95
|
+
(Number.isInteger(this.cachedScore)
|
|
96
|
+
? this.cachedScore | 0
|
|
97
|
+
: this.cachedScore
|
|
98
|
+
.toString()
|
|
99
|
+
.split('')
|
|
100
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
101
|
+
0;
|
|
102
|
+
return hash;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```typescript after
|
|
108
|
+
class User {
|
|
109
|
+
id: number;
|
|
110
|
+
name: string;
|
|
111
|
+
|
|
112
|
+
// Don't compare cached values
|
|
113
|
+
/** @hash({ skip: true }) */
|
|
114
|
+
cachedScore: number;
|
|
115
|
+
|
|
116
|
+
equals(other: unknown): boolean {
|
|
117
|
+
if (this === other) return true;
|
|
118
|
+
if (!(other instanceof User)) return false;
|
|
119
|
+
const typedOther = other as User;
|
|
120
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
hashCode(): number {
|
|
124
|
+
let hash = 17;
|
|
125
|
+
hash =
|
|
126
|
+
(hash * 31 +
|
|
127
|
+
(Number.isInteger(this.id)
|
|
128
|
+
? this.id | 0
|
|
129
|
+
: this.id
|
|
130
|
+
.toString()
|
|
131
|
+
.split('')
|
|
132
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
133
|
+
0;
|
|
134
|
+
hash =
|
|
135
|
+
(hash * 31 +
|
|
136
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
137
|
+
0;
|
|
138
|
+
hash =
|
|
139
|
+
(hash * 31 +
|
|
140
|
+
(Number.isInteger(this.cachedScore)
|
|
141
|
+
? this.cachedScore | 0
|
|
142
|
+
: this.cachedScore
|
|
143
|
+
.toString()
|
|
144
|
+
.split('')
|
|
145
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
146
|
+
0;
|
|
147
|
+
return hash;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Generated output:
|
|
153
|
+
|
|
154
|
+
```typescript before
|
|
155
|
+
class User {
|
|
156
|
+
id: number;
|
|
157
|
+
name: string;
|
|
158
|
+
|
|
159
|
+
// Don't compare cached values
|
|
160
|
+
/** @hash({ skip: true }) */
|
|
161
|
+
cachedScore: number;
|
|
162
|
+
|
|
163
|
+
equals(other: unknown): boolean {
|
|
164
|
+
if (this === other) return true;
|
|
165
|
+
if (!(other instanceof User)) return false;
|
|
166
|
+
const typedOther = other as User;
|
|
167
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
hashCode(): number {
|
|
171
|
+
let hash = 17;
|
|
172
|
+
hash =
|
|
173
|
+
(hash * 31 +
|
|
174
|
+
(Number.isInteger(this.id)
|
|
175
|
+
? this.id | 0
|
|
176
|
+
: this.id
|
|
177
|
+
.toString()
|
|
178
|
+
.split('')
|
|
179
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
180
|
+
0;
|
|
181
|
+
hash =
|
|
182
|
+
(hash * 31 +
|
|
183
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
184
|
+
0;
|
|
185
|
+
hash =
|
|
186
|
+
(hash * 31 +
|
|
187
|
+
(Number.isInteger(this.cachedScore)
|
|
188
|
+
? this.cachedScore | 0
|
|
189
|
+
: this.cachedScore
|
|
190
|
+
.toString()
|
|
191
|
+
.split('')
|
|
192
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
193
|
+
0;
|
|
194
|
+
return hash;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```typescript after
|
|
200
|
+
class User {
|
|
201
|
+
id: number;
|
|
202
|
+
name: string;
|
|
203
|
+
|
|
204
|
+
// Don't compare cached values
|
|
205
|
+
/** @hash({ skip: true }) */
|
|
206
|
+
cachedScore: number;
|
|
207
|
+
|
|
208
|
+
equals(other: unknown): boolean {
|
|
209
|
+
if (this === other) return true;
|
|
210
|
+
if (!(other instanceof User)) return false;
|
|
211
|
+
const typedOther = other as User;
|
|
212
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
hashCode(): number {
|
|
216
|
+
let hash = 17;
|
|
217
|
+
hash =
|
|
218
|
+
(hash * 31 +
|
|
219
|
+
(Number.isInteger(this.id)
|
|
220
|
+
? this.id | 0
|
|
221
|
+
: this.id
|
|
222
|
+
.toString()
|
|
223
|
+
.split('')
|
|
224
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
225
|
+
0;
|
|
226
|
+
hash =
|
|
227
|
+
(hash * 31 +
|
|
228
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
229
|
+
0;
|
|
230
|
+
hash =
|
|
231
|
+
(hash * 31 +
|
|
232
|
+
(Number.isInteger(this.cachedScore)
|
|
233
|
+
? this.cachedScore | 0
|
|
234
|
+
: this.cachedScore
|
|
235
|
+
.toString()
|
|
236
|
+
.split('')
|
|
237
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
238
|
+
0;
|
|
239
|
+
return hash;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Generated output:
|
|
245
|
+
|
|
246
|
+
```typescript before
|
|
247
|
+
class User {
|
|
248
|
+
id: number;
|
|
249
|
+
name: string;
|
|
250
|
+
|
|
251
|
+
// Don't compare cached values
|
|
252
|
+
/** @hash({ skip: true }) */
|
|
253
|
+
cachedScore: number;
|
|
254
|
+
|
|
255
|
+
equals(other: unknown): boolean {
|
|
256
|
+
if (this === other) return true;
|
|
257
|
+
if (!(other instanceof User)) return false;
|
|
258
|
+
const typedOther = other as User;
|
|
259
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
hashCode(): number {
|
|
263
|
+
let hash = 17;
|
|
264
|
+
hash =
|
|
265
|
+
(hash * 31 +
|
|
266
|
+
(Number.isInteger(this.id)
|
|
267
|
+
? this.id | 0
|
|
268
|
+
: this.id
|
|
269
|
+
.toString()
|
|
270
|
+
.split('')
|
|
271
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
272
|
+
0;
|
|
273
|
+
hash =
|
|
274
|
+
(hash * 31 +
|
|
275
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
276
|
+
0;
|
|
277
|
+
hash =
|
|
278
|
+
(hash * 31 +
|
|
279
|
+
(Number.isInteger(this.cachedScore)
|
|
280
|
+
? this.cachedScore | 0
|
|
281
|
+
: this.cachedScore
|
|
282
|
+
.toString()
|
|
283
|
+
.split('')
|
|
284
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
285
|
+
0;
|
|
286
|
+
return hash;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```typescript after
|
|
292
|
+
class User {
|
|
293
|
+
id: number;
|
|
294
|
+
name: string;
|
|
295
|
+
|
|
296
|
+
// Don't compare cached values
|
|
297
|
+
/** @hash({ skip: true }) */
|
|
298
|
+
cachedScore: number;
|
|
299
|
+
|
|
300
|
+
equals(other: unknown): boolean {
|
|
301
|
+
if (this === other) return true;
|
|
302
|
+
if (!(other instanceof User)) return false;
|
|
303
|
+
const typedOther = other as User;
|
|
304
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
hashCode(): number {
|
|
308
|
+
let hash = 17;
|
|
309
|
+
hash =
|
|
310
|
+
(hash * 31 +
|
|
311
|
+
(Number.isInteger(this.id)
|
|
312
|
+
? this.id | 0
|
|
313
|
+
: this.id
|
|
314
|
+
.toString()
|
|
315
|
+
.split('')
|
|
316
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
317
|
+
0;
|
|
318
|
+
hash =
|
|
319
|
+
(hash * 31 +
|
|
320
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
321
|
+
0;
|
|
322
|
+
hash =
|
|
323
|
+
(hash * 31 +
|
|
324
|
+
(Number.isInteger(this.cachedScore)
|
|
325
|
+
? this.cachedScore | 0
|
|
326
|
+
: this.cachedScore
|
|
327
|
+
.toString()
|
|
328
|
+
.split('')
|
|
329
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
330
|
+
0;
|
|
331
|
+
return hash;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Generated output:
|
|
337
|
+
|
|
338
|
+
```typescript before
|
|
339
|
+
class User {
|
|
340
|
+
id: number;
|
|
341
|
+
name: string;
|
|
342
|
+
|
|
343
|
+
// Don't compare cached values
|
|
344
|
+
/** @hash({ skip: true }) */
|
|
345
|
+
cachedScore: number;
|
|
346
|
+
|
|
347
|
+
equals(other: unknown): boolean {
|
|
348
|
+
if (this === other) return true;
|
|
349
|
+
if (!(other instanceof User)) return false;
|
|
350
|
+
const typedOther = other as User;
|
|
351
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
hashCode(): number {
|
|
355
|
+
let hash = 17;
|
|
356
|
+
hash =
|
|
357
|
+
(hash * 31 +
|
|
358
|
+
(Number.isInteger(this.id)
|
|
359
|
+
? this.id | 0
|
|
360
|
+
: this.id
|
|
361
|
+
.toString()
|
|
362
|
+
.split('')
|
|
363
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
364
|
+
0;
|
|
365
|
+
hash =
|
|
366
|
+
(hash * 31 +
|
|
367
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
368
|
+
0;
|
|
369
|
+
hash =
|
|
370
|
+
(hash * 31 +
|
|
371
|
+
(Number.isInteger(this.cachedScore)
|
|
372
|
+
? this.cachedScore | 0
|
|
373
|
+
: this.cachedScore
|
|
374
|
+
.toString()
|
|
375
|
+
.split('')
|
|
376
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
377
|
+
0;
|
|
378
|
+
return hash;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
```typescript after
|
|
384
|
+
class User {
|
|
385
|
+
id: number;
|
|
386
|
+
name: string;
|
|
387
|
+
|
|
388
|
+
// Don't compare cached values
|
|
389
|
+
/** @hash({ skip: true }) */
|
|
390
|
+
cachedScore: number;
|
|
391
|
+
|
|
392
|
+
equals(other: unknown): boolean {
|
|
393
|
+
if (this === other) return true;
|
|
394
|
+
if (!(other instanceof User)) return false;
|
|
395
|
+
const typedOther = other as User;
|
|
396
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
hashCode(): number {
|
|
400
|
+
let hash = 17;
|
|
401
|
+
hash =
|
|
402
|
+
(hash * 31 +
|
|
403
|
+
(Number.isInteger(this.id)
|
|
404
|
+
? this.id | 0
|
|
405
|
+
: this.id
|
|
406
|
+
.toString()
|
|
407
|
+
.split('')
|
|
408
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
409
|
+
0;
|
|
410
|
+
hash =
|
|
411
|
+
(hash * 31 +
|
|
412
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
413
|
+
0;
|
|
414
|
+
hash =
|
|
415
|
+
(hash * 31 +
|
|
416
|
+
(Number.isInteger(this.cachedScore)
|
|
417
|
+
? this.cachedScore | 0
|
|
418
|
+
: this.cachedScore
|
|
419
|
+
.toString()
|
|
420
|
+
.split('')
|
|
421
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
422
|
+
0;
|
|
423
|
+
return hash;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Generated output:
|
|
429
|
+
|
|
430
|
+
```typescript before
|
|
431
|
+
class User {
|
|
432
|
+
id: number;
|
|
433
|
+
name: string;
|
|
434
|
+
|
|
435
|
+
// Don't compare cached values
|
|
436
|
+
/** @hash({ skip: true }) */
|
|
437
|
+
cachedScore: number;
|
|
438
|
+
|
|
439
|
+
equals(other: unknown): boolean {
|
|
440
|
+
if (this === other) return true;
|
|
441
|
+
if (!(other instanceof User)) return false;
|
|
442
|
+
const typedOther = other as User;
|
|
443
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
hashCode(): number {
|
|
447
|
+
let hash = 17;
|
|
448
|
+
hash =
|
|
449
|
+
(hash * 31 +
|
|
450
|
+
(Number.isInteger(this.id)
|
|
451
|
+
? this.id | 0
|
|
452
|
+
: this.id
|
|
453
|
+
.toString()
|
|
454
|
+
.split('')
|
|
455
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
456
|
+
0;
|
|
457
|
+
hash =
|
|
458
|
+
(hash * 31 +
|
|
459
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
460
|
+
0;
|
|
461
|
+
hash =
|
|
462
|
+
(hash * 31 +
|
|
463
|
+
(Number.isInteger(this.cachedScore)
|
|
464
|
+
? this.cachedScore | 0
|
|
465
|
+
: this.cachedScore
|
|
466
|
+
.toString()
|
|
467
|
+
.split('')
|
|
468
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
469
|
+
0;
|
|
470
|
+
return hash;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```typescript after
|
|
476
|
+
class User {
|
|
477
|
+
id: number;
|
|
478
|
+
name: string;
|
|
479
|
+
|
|
480
|
+
// Don't compare cached values
|
|
481
|
+
/** @hash({ skip: true }) */
|
|
482
|
+
cachedScore: number;
|
|
483
|
+
|
|
484
|
+
equals(other: unknown): boolean {
|
|
485
|
+
if (this === other) return true;
|
|
486
|
+
if (!(other instanceof User)) return false;
|
|
487
|
+
const typedOther = other as User;
|
|
488
|
+
return this.id === typedOther.id && this.name === typedOther.name;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
hashCode(): number {
|
|
492
|
+
let hash = 17;
|
|
493
|
+
hash =
|
|
494
|
+
(hash * 31 +
|
|
495
|
+
(Number.isInteger(this.id)
|
|
496
|
+
? this.id | 0
|
|
497
|
+
: this.id
|
|
498
|
+
.toString()
|
|
499
|
+
.split('')
|
|
500
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
501
|
+
0;
|
|
502
|
+
hash =
|
|
503
|
+
(hash * 31 +
|
|
504
|
+
(this.name ?? '').split('').reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0)) |
|
|
505
|
+
0;
|
|
506
|
+
hash =
|
|
507
|
+
(hash * 31 +
|
|
508
|
+
(Number.isInteger(this.cachedScore)
|
|
509
|
+
? this.cachedScore | 0
|
|
510
|
+
: this.cachedScore
|
|
511
|
+
.toString()
|
|
512
|
+
.split('')
|
|
513
|
+
.reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 0))) |
|
|
514
|
+
0;
|
|
515
|
+
return hash;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Equality Contract
|
|
521
|
+
|
|
522
|
+
When implementing `PartialEq`, consider also implementing `Hash`:
|
|
523
|
+
|
|
524
|
+
- **Reflexivity**: `a.equals(a)` is always true
|
|
525
|
+
- **Symmetry**: `a.equals(b)` implies `b.equals(a)`
|
|
526
|
+
- **Hash consistency**: Equal objects must have equal hash codes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# PartialEq
|
|
2
|
+
|
|
3
|
+
The `PartialEq` macro generates an `equals()` method for field-by-field
|
|
4
|
+
structural equality comparison. This is analogous to Rust's `PartialEq` trait,
|
|
5
|
+
enabling value-based equality semantics instead of reference equality.
|
|
6
|
+
|
|
7
|
+
## Generated Output
|
|
8
|
+
|
|
9
|
+
| Type | Generated Code | Description |
|
|
10
|
+
|------|----------------|-------------|
|
|
11
|
+
| Class | `equals(other: unknown): boolean` | Instance method with instanceof check |
|
|
12
|
+
| Enum | `equalsEnumName(a: EnumName, b: EnumName): boolean` | Standalone function using strict equality |
|
|
13
|
+
| Interface | `equalsInterfaceName(a: InterfaceName, b: InterfaceName): boolean` | Standalone function comparing fields |
|
|
14
|
+
| Type Alias | `equalsTypeName(a: TypeName, b: TypeName): boolean` | Standalone function with type-appropriate comparison |
|
|
15
|
+
|
|
16
|
+
## Comparison Strategy
|
|
17
|
+
|
|
18
|
+
The generated equality check:
|
|
19
|
+
|
|
20
|
+
1. **Identity check**: `this === other` returns true immediately
|
|
21
|
+
2. **Type check**: For classes, uses `instanceof`; returns false if wrong type
|
|
22
|
+
3. **Field comparison**: Compares each non-skipped field
|
|
23
|
+
|
|
24
|
+
## Type-Specific Comparisons
|
|
25
|
+
|
|
26
|
+
| Type | Comparison Method |
|
|
27
|
+
|------|-------------------|
|
|
28
|
+
| Primitives | Strict equality (`===`) |
|
|
29
|
+
| Arrays | Length + element-by-element (recursive) |
|
|
30
|
+
| `Date` | `getTime()` comparison |
|
|
31
|
+
| `Map` | Size + entry-by-entry comparison |
|
|
32
|
+
| `Set` | Size + membership check |
|
|
33
|
+
| Objects | Calls `equals()` if available, else `===` |
|
|
34
|
+
|
|
35
|
+
## Field-Level Options
|
|
36
|
+
|
|
37
|
+
The `@partialEq` decorator supports:
|
|
38
|
+
|
|
39
|
+
- `skip` - Exclude the field from equality comparison
|