nalloc 0.4.0 → 0.5.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.
- package/README.md +300 -180
- package/build/codemod-cli.cjs +153 -0
- package/build/codemod-cli.cjs.map +1 -0
- package/build/codemod-cli.d.ts +2 -0
- package/build/codemod-cli.js +103 -0
- package/build/codemod-cli.js.map +1 -0
- package/build/codemod.cjs +652 -0
- package/build/codemod.cjs.map +1 -0
- package/build/codemod.d.ts +29 -0
- package/build/codemod.js +634 -0
- package/build/codemod.js.map +1 -0
- package/build/eslint.cjs +221 -0
- package/build/eslint.cjs.map +1 -0
- package/build/eslint.d.ts +36 -0
- package/build/eslint.js +198 -0
- package/build/eslint.js.map +1 -0
- package/build/http.cjs +31 -0
- package/build/http.cjs.map +1 -0
- package/build/http.d.ts +31 -0
- package/build/http.js +13 -0
- package/build/http.js.map +1 -0
- package/build/result.cjs +0 -11
- package/build/result.cjs.map +1 -1
- package/build/result.d.ts +0 -32
- package/build/result.js +0 -8
- package/build/result.js.map +1 -1
- package/build/safe.cjs +4 -0
- package/build/safe.cjs.map +1 -1
- package/build/safe.d.ts +1 -0
- package/build/safe.js +1 -0
- package/build/safe.js.map +1 -1
- package/build/schema.cjs +32 -0
- package/build/schema.cjs.map +1 -0
- package/build/schema.d.ts +44 -0
- package/build/schema.js +14 -0
- package/build/schema.js.map +1 -0
- package/package.json +54 -5
- package/src/__tests__/codemod.ts +211 -0
- package/src/__tests__/eslint.ts +99 -0
- package/src/__tests__/fixtures/tsconfig.json +10 -0
- package/src/__tests__/http.ts +64 -0
- package/src/__tests__/result.ts +74 -125
- package/src/__tests__/schema.ts +58 -0
- package/src/codemod-cli.ts +108 -0
- package/src/codemod.ts +623 -0
- package/src/eslint.ts +145 -0
- package/src/http.ts +42 -0
- package/src/result.ts +0 -37
- package/src/safe.ts +1 -0
- package/src/schema.ts +52 -0
package/src/__tests__/result.ts
CHANGED
|
@@ -49,11 +49,9 @@ import {
|
|
|
49
49
|
safeTry,
|
|
50
50
|
safeTryAsync,
|
|
51
51
|
fromPromise,
|
|
52
|
-
fromSchema,
|
|
53
52
|
gen,
|
|
54
|
-
genAsync
|
|
53
|
+
genAsync,
|
|
55
54
|
} from '../result.js';
|
|
56
|
-
import type { StandardSchema } from '../result.js';
|
|
57
55
|
import { ok, err, isOk, isErr, optionOf as optOf, none } from '../types.js';
|
|
58
56
|
|
|
59
57
|
describe('Result', () => {
|
|
@@ -86,7 +84,6 @@ describe('Result', () => {
|
|
|
86
84
|
expect(isErr(failure)).toBe(true);
|
|
87
85
|
expect((failure as any).error.message).toBe('failed');
|
|
88
86
|
});
|
|
89
|
-
|
|
90
87
|
});
|
|
91
88
|
|
|
92
89
|
describe('type guards', () => {
|
|
@@ -127,7 +124,7 @@ describe('Result', () => {
|
|
|
127
124
|
|
|
128
125
|
describe('map', () => {
|
|
129
126
|
it('maps Ok value', () => {
|
|
130
|
-
const result = map(ok(2), x => x * 3);
|
|
127
|
+
const result = map(ok(2), (x) => x * 3);
|
|
131
128
|
expect(unwrap(result)).toBe(6);
|
|
132
129
|
});
|
|
133
130
|
|
|
@@ -140,7 +137,7 @@ describe('Result', () => {
|
|
|
140
137
|
|
|
141
138
|
describe('mapErr', () => {
|
|
142
139
|
it('maps Err value', () => {
|
|
143
|
-
const result = mapErr(err('error'), e => e.toUpperCase());
|
|
140
|
+
const result = mapErr(err('error'), (e) => e.toUpperCase());
|
|
144
141
|
expect(isErr(result)).toBe(true);
|
|
145
142
|
expect((result as any).error).toBe('ERROR');
|
|
146
143
|
});
|
|
@@ -154,12 +151,12 @@ describe('Result', () => {
|
|
|
154
151
|
|
|
155
152
|
describe('flatMap', () => {
|
|
156
153
|
it('flatMaps Ok value', () => {
|
|
157
|
-
const result = flatMap(ok(2), x => ok(x * 3));
|
|
154
|
+
const result = flatMap(ok(2), (x) => ok(x * 3));
|
|
158
155
|
expect(unwrap(result)).toBe(6);
|
|
159
156
|
});
|
|
160
157
|
|
|
161
158
|
it('can return Err from mapper', () => {
|
|
162
|
-
const result = flatMap(ok(2), x => err(`error: ${x}`));
|
|
159
|
+
const result = flatMap(ok(2), (x) => err(`error: ${x}`));
|
|
163
160
|
expect(isErr(result)).toBe(true);
|
|
164
161
|
expect((result as any).error).toBe('error: 2');
|
|
165
162
|
});
|
|
@@ -173,7 +170,7 @@ describe('Result', () => {
|
|
|
173
170
|
|
|
174
171
|
describe('andThen', () => {
|
|
175
172
|
it('aliases flatMap for Ok', () => {
|
|
176
|
-
const result = andThen(ok(2), x => ok(x * 4));
|
|
173
|
+
const result = andThen(ok(2), (x) => ok(x * 4));
|
|
177
174
|
expect(unwrap(result)).toBe(8);
|
|
178
175
|
});
|
|
179
176
|
|
|
@@ -188,7 +185,7 @@ describe('Result', () => {
|
|
|
188
185
|
it('runs for Ok and returns original', () => {
|
|
189
186
|
let seen = 0;
|
|
190
187
|
const res = ok(7);
|
|
191
|
-
const out = tap(res, value => {
|
|
188
|
+
const out = tap(res, (value) => {
|
|
192
189
|
seen = value;
|
|
193
190
|
});
|
|
194
191
|
expect(out).toBe(res);
|
|
@@ -209,7 +206,7 @@ describe('Result', () => {
|
|
|
209
206
|
it('runs for Err and returns original', () => {
|
|
210
207
|
let seen = '';
|
|
211
208
|
const res = err('nope');
|
|
212
|
-
const out = tapErr(res, error => {
|
|
209
|
+
const out = tapErr(res, (error) => {
|
|
213
210
|
seen = error;
|
|
214
211
|
});
|
|
215
212
|
expect(out).toBe(res);
|
|
@@ -228,12 +225,20 @@ describe('Result', () => {
|
|
|
228
225
|
|
|
229
226
|
describe('bimap', () => {
|
|
230
227
|
it('maps Ok value', () => {
|
|
231
|
-
const result = bimap(
|
|
228
|
+
const result = bimap(
|
|
229
|
+
ok(2),
|
|
230
|
+
(x) => x * 3,
|
|
231
|
+
(e) => e,
|
|
232
|
+
);
|
|
232
233
|
expect(unwrap(result)).toBe(6);
|
|
233
234
|
});
|
|
234
235
|
|
|
235
236
|
it('maps Err value', () => {
|
|
236
|
-
const result = bimap(
|
|
237
|
+
const result = bimap(
|
|
238
|
+
err('error'),
|
|
239
|
+
(x: number) => x * 3,
|
|
240
|
+
(e) => e.toUpperCase(),
|
|
241
|
+
);
|
|
237
242
|
expect(isErr(result)).toBe(true);
|
|
238
243
|
expect((result as any).error).toBe('ERROR');
|
|
239
244
|
});
|
|
@@ -247,7 +252,11 @@ describe('Result', () => {
|
|
|
247
252
|
it('throws error value for Err', () => {
|
|
248
253
|
const e = err('error');
|
|
249
254
|
let caught: unknown;
|
|
250
|
-
try {
|
|
255
|
+
try {
|
|
256
|
+
unwrap(e);
|
|
257
|
+
} catch (x) {
|
|
258
|
+
caught = x;
|
|
259
|
+
}
|
|
251
260
|
expect(caught).toBe('error');
|
|
252
261
|
});
|
|
253
262
|
});
|
|
@@ -279,7 +288,7 @@ describe('Result', () => {
|
|
|
279
288
|
|
|
280
289
|
it('calls function for Err', () => {
|
|
281
290
|
let called = false;
|
|
282
|
-
const result = unwrapOrElse(err('error'), e => {
|
|
291
|
+
const result = unwrapOrElse(err('error'), (e) => {
|
|
283
292
|
called = true;
|
|
284
293
|
expect(e).toBe('error');
|
|
285
294
|
return 99;
|
|
@@ -291,7 +300,7 @@ describe('Result', () => {
|
|
|
291
300
|
|
|
292
301
|
describe('mapOr', () => {
|
|
293
302
|
it('maps Ok value', () => {
|
|
294
|
-
const result = mapOr(ok(2), 10, x => x * 3);
|
|
303
|
+
const result = mapOr(ok(2), 10, (x) => x * 3);
|
|
295
304
|
expect(result).toBe(6);
|
|
296
305
|
});
|
|
297
306
|
|
|
@@ -303,16 +312,24 @@ describe('Result', () => {
|
|
|
303
312
|
|
|
304
313
|
describe('mapOrElse', () => {
|
|
305
314
|
it('maps Ok value', () => {
|
|
306
|
-
const result = mapOrElse(
|
|
315
|
+
const result = mapOrElse(
|
|
316
|
+
ok(2),
|
|
317
|
+
() => 10,
|
|
318
|
+
(x) => x * 3,
|
|
319
|
+
);
|
|
307
320
|
expect(result).toBe(6);
|
|
308
321
|
});
|
|
309
322
|
|
|
310
323
|
it('returns default for Err', () => {
|
|
311
324
|
let called = false;
|
|
312
|
-
const result = mapOrElse(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
325
|
+
const result = mapOrElse(
|
|
326
|
+
err('error'),
|
|
327
|
+
() => {
|
|
328
|
+
called = true;
|
|
329
|
+
return 10;
|
|
330
|
+
},
|
|
331
|
+
(x: number) => x * 3,
|
|
332
|
+
);
|
|
316
333
|
expect(result).toBe(10);
|
|
317
334
|
expect(called).toBe(true);
|
|
318
335
|
});
|
|
@@ -383,7 +400,7 @@ describe('Result', () => {
|
|
|
383
400
|
|
|
384
401
|
it('calls function if Err', () => {
|
|
385
402
|
let called = false;
|
|
386
|
-
const result = orElse(err('error'), e => {
|
|
403
|
+
const result = orElse(err('error'), (e) => {
|
|
387
404
|
called = true;
|
|
388
405
|
expect(e).toBe('error');
|
|
389
406
|
return ok(2);
|
|
@@ -462,7 +479,6 @@ describe('Result', () => {
|
|
|
462
479
|
expect(unwrap(result)).toBe(42);
|
|
463
480
|
});
|
|
464
481
|
|
|
465
|
-
|
|
466
482
|
it('returns outer Err', () => {
|
|
467
483
|
const result = flatten(err('outer error'));
|
|
468
484
|
expect(isErr(result)).toBe(true);
|
|
@@ -474,8 +490,8 @@ describe('Result', () => {
|
|
|
474
490
|
it('calls ok branch for Ok', () => {
|
|
475
491
|
const result = match(
|
|
476
492
|
ok(42),
|
|
477
|
-
x => `value: ${x}`,
|
|
478
|
-
e => `error: ${e}`,
|
|
493
|
+
(x) => `value: ${x}`,
|
|
494
|
+
(e) => `error: ${e}`,
|
|
479
495
|
);
|
|
480
496
|
expect(result).toBe('value: 42');
|
|
481
497
|
});
|
|
@@ -484,7 +500,7 @@ describe('Result', () => {
|
|
|
484
500
|
const result = match(
|
|
485
501
|
err('failed'),
|
|
486
502
|
(x: number) => `value: ${x}`,
|
|
487
|
-
e => `error: ${e}`,
|
|
503
|
+
(e) => `error: ${e}`,
|
|
488
504
|
);
|
|
489
505
|
expect(result).toBe('error: failed');
|
|
490
506
|
});
|
|
@@ -566,11 +582,11 @@ describe('Result', () => {
|
|
|
566
582
|
|
|
567
583
|
describe('isOkAnd', () => {
|
|
568
584
|
it('returns true if Ok and predicate true', () => {
|
|
569
|
-
expect(isOkAnd(ok(5), x => x > 3)).toBe(true);
|
|
585
|
+
expect(isOkAnd(ok(5), (x) => x > 3)).toBe(true);
|
|
570
586
|
});
|
|
571
587
|
|
|
572
588
|
it('returns false if Ok and predicate false', () => {
|
|
573
|
-
expect(isOkAnd(ok(2), x => x > 3)).toBe(false);
|
|
589
|
+
expect(isOkAnd(ok(2), (x) => x > 3)).toBe(false);
|
|
574
590
|
});
|
|
575
591
|
|
|
576
592
|
it('returns false for Err', () => {
|
|
@@ -580,11 +596,11 @@ describe('Result', () => {
|
|
|
580
596
|
|
|
581
597
|
describe('isErrAnd', () => {
|
|
582
598
|
it('returns true if Err and predicate true', () => {
|
|
583
|
-
expect(isErrAnd(err('error'), e => e.length > 3)).toBe(true);
|
|
599
|
+
expect(isErrAnd(err('error'), (e) => e.length > 3)).toBe(true);
|
|
584
600
|
});
|
|
585
601
|
|
|
586
602
|
it('returns false if Err and predicate false', () => {
|
|
587
|
-
expect(isErrAnd(err('no'), e => e.length > 3)).toBe(false);
|
|
603
|
+
expect(isErrAnd(err('no'), (e) => e.length > 3)).toBe(false);
|
|
588
604
|
});
|
|
589
605
|
|
|
590
606
|
it('returns false for Ok', () => {
|
|
@@ -598,12 +614,11 @@ describe('Result', () => {
|
|
|
598
614
|
() => {
|
|
599
615
|
throw new Error('boom');
|
|
600
616
|
},
|
|
601
|
-
error => (error as Error).message,
|
|
617
|
+
(error) => (error as Error).message,
|
|
602
618
|
);
|
|
603
619
|
expect(isErr(result)).toBe(true);
|
|
604
620
|
expect((result as any).error).toBe('boom');
|
|
605
621
|
});
|
|
606
|
-
|
|
607
622
|
});
|
|
608
623
|
|
|
609
624
|
describe('wrap', () => {
|
|
@@ -643,7 +658,7 @@ describe('Result', () => {
|
|
|
643
658
|
});
|
|
644
659
|
|
|
645
660
|
it('throws Err error for failed Result', () => {
|
|
646
|
-
const fn = (x: number) => x > 0 ? ok(x) : err('negative');
|
|
661
|
+
const fn = (x: number) => (x > 0 ? ok(x) : err('negative'));
|
|
647
662
|
const throwing = toThrowable(fn);
|
|
648
663
|
expect(() => throwing(-1)).toThrow('negative');
|
|
649
664
|
});
|
|
@@ -715,22 +730,14 @@ describe('Result', () => {
|
|
|
715
730
|
});
|
|
716
731
|
|
|
717
732
|
it('partitionAsync separates async results', async () => {
|
|
718
|
-
const promises = [
|
|
719
|
-
Promise.resolve(ok(1)),
|
|
720
|
-
Promise.resolve(err('a')),
|
|
721
|
-
Promise.resolve(ok(2)),
|
|
722
|
-
];
|
|
733
|
+
const promises = [Promise.resolve(ok(1)), Promise.resolve(err('a')), Promise.resolve(ok(2))];
|
|
723
734
|
const [oks, errs] = await partitionAsync(promises);
|
|
724
735
|
expect(oks).toEqual([1, 2]);
|
|
725
736
|
expect(errs).toEqual(['a']);
|
|
726
737
|
});
|
|
727
738
|
|
|
728
739
|
it('partitionAsync handles rejected promises as errors', async () => {
|
|
729
|
-
const promises = [
|
|
730
|
-
Promise.resolve(ok(1)),
|
|
731
|
-
Promise.reject(new Error('rejected')),
|
|
732
|
-
Promise.resolve(ok(2)),
|
|
733
|
-
];
|
|
740
|
+
const promises = [Promise.resolve(ok(1)), Promise.reject(new Error('rejected')), Promise.resolve(ok(2))];
|
|
734
741
|
const [oks, errs] = await partitionAsync(promises);
|
|
735
742
|
expect(oks).toEqual([1, 2]);
|
|
736
743
|
expect(errs).toHaveLength(1);
|
|
@@ -779,8 +786,10 @@ describe('Result', () => {
|
|
|
779
786
|
|
|
780
787
|
it('uses onError mapper for sync throw', () => {
|
|
781
788
|
const result = tryCatchMaybePromise(
|
|
782
|
-
() => {
|
|
783
|
-
|
|
789
|
+
() => {
|
|
790
|
+
throw new Error('fail');
|
|
791
|
+
},
|
|
792
|
+
(e) => `mapped: ${(e as Error).message}`,
|
|
784
793
|
);
|
|
785
794
|
expect(isErr(result)).toBe(true);
|
|
786
795
|
expect((result as any).error).toBe('mapped: fail');
|
|
@@ -789,7 +798,7 @@ describe('Result', () => {
|
|
|
789
798
|
it('uses onError mapper for async rejection', async () => {
|
|
790
799
|
const result = await tryCatchMaybePromise(
|
|
791
800
|
() => Promise.reject(new Error('fail')),
|
|
792
|
-
(e) => `mapped: ${(e as Error).message}
|
|
801
|
+
(e) => `mapped: ${(e as Error).message}`,
|
|
793
802
|
);
|
|
794
803
|
expect(isErr(result)).toBe(true);
|
|
795
804
|
expect((result as any).error).toBe('mapped: fail');
|
|
@@ -818,10 +827,7 @@ describe('Result', () => {
|
|
|
818
827
|
});
|
|
819
828
|
|
|
820
829
|
it('handles all async values', async () => {
|
|
821
|
-
const result = await settleMaybePromise([
|
|
822
|
-
Promise.resolve(1),
|
|
823
|
-
Promise.resolve(2)
|
|
824
|
-
]);
|
|
830
|
+
const result = await settleMaybePromise([Promise.resolve(1), Promise.resolve(2)]);
|
|
825
831
|
expect(result).toEqual([1, 2]);
|
|
826
832
|
});
|
|
827
833
|
|
|
@@ -846,28 +852,20 @@ describe('Result', () => {
|
|
|
846
852
|
});
|
|
847
853
|
|
|
848
854
|
it('preserves input order with interleaved sync/async', async () => {
|
|
849
|
-
const result = await partitionMaybePromise([
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
err('b'),
|
|
855
|
+
const result = await partitionMaybePromise([Promise.resolve(ok(1)), ok(2), Promise.resolve(err('a')), err('b')]);
|
|
856
|
+
expect(result).toEqual([
|
|
857
|
+
[1, 2],
|
|
858
|
+
['a', 'b'],
|
|
854
859
|
]);
|
|
855
|
-
expect(result).toEqual([[1, 2], ['a', 'b']]);
|
|
856
860
|
});
|
|
857
861
|
|
|
858
862
|
it('handles all async values', async () => {
|
|
859
|
-
const result = await partitionMaybePromise([
|
|
860
|
-
Promise.resolve(ok(1)),
|
|
861
|
-
Promise.resolve(err('a'))
|
|
862
|
-
]);
|
|
863
|
+
const result = await partitionMaybePromise([Promise.resolve(ok(1)), Promise.resolve(err('a'))]);
|
|
863
864
|
expect(result).toEqual([[1], ['a']]);
|
|
864
865
|
});
|
|
865
866
|
|
|
866
867
|
it('handles rejected promises as errors', async () => {
|
|
867
|
-
const result = await partitionMaybePromise([
|
|
868
|
-
ok(1),
|
|
869
|
-
Promise.reject(new Error('rejected'))
|
|
870
|
-
]);
|
|
868
|
+
const result = await partitionMaybePromise([ok(1), Promise.reject(new Error('rejected'))]);
|
|
871
869
|
expect(result[0]).toEqual([1]);
|
|
872
870
|
expect(result[1]).toHaveLength(1);
|
|
873
871
|
expect((result[1][0] as Error).message).toBe('rejected');
|
|
@@ -938,7 +936,7 @@ describe('Result', () => {
|
|
|
938
936
|
});
|
|
939
937
|
|
|
940
938
|
it('propagates Err through chain', () => {
|
|
941
|
-
const parse = (s: string) => s === 'bad' ? err('parse error') : ok(Number(s));
|
|
939
|
+
const parse = (s: string) => (s === 'bad' ? err('parse error') : ok(Number(s)));
|
|
942
940
|
const result = safeTry(() => {
|
|
943
941
|
const a = unwrap(parse('10'));
|
|
944
942
|
const b = unwrap(parse('bad'));
|
|
@@ -995,10 +993,7 @@ describe('Result', () => {
|
|
|
995
993
|
});
|
|
996
994
|
|
|
997
995
|
it('uses onError mapper for rejected promise', async () => {
|
|
998
|
-
const result = await fromPromise(
|
|
999
|
-
Promise.reject(new Error('boom')),
|
|
1000
|
-
(e) => `mapped: ${(e as Error).message}`,
|
|
1001
|
-
);
|
|
996
|
+
const result = await fromPromise(Promise.reject(new Error('boom')), (e) => `mapped: ${(e as Error).message}`);
|
|
1002
997
|
expect(isErr(result)).toBe(true);
|
|
1003
998
|
expect((result as { error: string }).error).toBe('mapped: boom');
|
|
1004
999
|
});
|
|
@@ -1016,53 +1011,9 @@ describe('Result', () => {
|
|
|
1016
1011
|
});
|
|
1017
1012
|
});
|
|
1018
1013
|
|
|
1019
|
-
describe('fromSchema', () => {
|
|
1020
|
-
const validSchema: StandardSchema<string> = {
|
|
1021
|
-
'~standard': {
|
|
1022
|
-
validate: (value) => typeof value === 'string'
|
|
1023
|
-
? { value }
|
|
1024
|
-
: { issues: [{ message: 'Expected string' }] },
|
|
1025
|
-
},
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
const asyncSchema: StandardSchema<number> = {
|
|
1029
|
-
'~standard': {
|
|
1030
|
-
validate: (value) => Promise.resolve(
|
|
1031
|
-
typeof value === 'number'
|
|
1032
|
-
? { value }
|
|
1033
|
-
: { issues: [{ message: 'Expected number' }] },
|
|
1034
|
-
),
|
|
1035
|
-
},
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
it('returns Ok for valid sync schema', () => {
|
|
1039
|
-
const result = fromSchema(validSchema, 'hello');
|
|
1040
|
-
expect(isOk(result)).toBe(true);
|
|
1041
|
-
expect(result).toBe('hello');
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
it('returns Err with issues for invalid sync schema', () => {
|
|
1045
|
-
const result = fromSchema(validSchema, 42);
|
|
1046
|
-
expect(isErr(result)).toBe(true);
|
|
1047
|
-
expect((result as any).error).toEqual([{ message: 'Expected string' }]);
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
it('returns Ok for valid async schema', async () => {
|
|
1051
|
-
const result = await fromSchema(asyncSchema, 42);
|
|
1052
|
-
expect(isOk(result)).toBe(true);
|
|
1053
|
-
expect(result).toBe(42);
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
it('returns Err for invalid async schema', async () => {
|
|
1057
|
-
const result = await fromSchema(asyncSchema, 'hello');
|
|
1058
|
-
expect(isErr(result)).toBe(true);
|
|
1059
|
-
expect((result as any).error).toEqual([{ message: 'Expected number' }]);
|
|
1060
|
-
});
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
1014
|
describe('gen', () => {
|
|
1064
1015
|
it('returns Ok for successful execution', () => {
|
|
1065
|
-
const result = gen(function*($) {
|
|
1016
|
+
const result = gen(function* ($) {
|
|
1066
1017
|
const a = yield* $(ok(10));
|
|
1067
1018
|
const b = yield* $(ok(5));
|
|
1068
1019
|
return a + b;
|
|
@@ -1072,7 +1023,7 @@ describe('Result', () => {
|
|
|
1072
1023
|
});
|
|
1073
1024
|
|
|
1074
1025
|
it('short-circuits on first Err', () => {
|
|
1075
|
-
const result = gen(function*($) {
|
|
1026
|
+
const result = gen(function* ($) {
|
|
1076
1027
|
const a = yield* $(ok(10));
|
|
1077
1028
|
const b = yield* $(err('fail') as Result<number, string>);
|
|
1078
1029
|
const c = yield* $(ok(5));
|
|
@@ -1083,8 +1034,8 @@ describe('Result', () => {
|
|
|
1083
1034
|
});
|
|
1084
1035
|
|
|
1085
1036
|
it('propagates Err through chain', () => {
|
|
1086
|
-
const parse = (s: string) => s === 'bad' ? err('parse error') : ok(Number(s));
|
|
1087
|
-
const result = gen(function*($) {
|
|
1037
|
+
const parse = (s: string) => (s === 'bad' ? err('parse error') : ok(Number(s)));
|
|
1038
|
+
const result = gen(function* ($) {
|
|
1088
1039
|
const a = yield* $(parse('10'));
|
|
1089
1040
|
const b = yield* $(parse('bad'));
|
|
1090
1041
|
const c = yield* $(parse('5'));
|
|
@@ -1095,7 +1046,7 @@ describe('Result', () => {
|
|
|
1095
1046
|
});
|
|
1096
1047
|
|
|
1097
1048
|
it('returns Ok for empty generator', () => {
|
|
1098
|
-
const result = gen(function*() {
|
|
1049
|
+
const result = gen(function* () {
|
|
1099
1050
|
return 42;
|
|
1100
1051
|
});
|
|
1101
1052
|
expect(isOk(result)).toBe(true);
|
|
@@ -1104,7 +1055,7 @@ describe('Result', () => {
|
|
|
1104
1055
|
|
|
1105
1056
|
it('runs try/finally cleanup when short-circuiting on Err', () => {
|
|
1106
1057
|
let cleanedUp = false;
|
|
1107
|
-
const result = gen(function*($) {
|
|
1058
|
+
const result = gen(function* ($) {
|
|
1108
1059
|
try {
|
|
1109
1060
|
const a = yield* $(ok(10));
|
|
1110
1061
|
const b = yield* $(err('fail') as Result<number, string>);
|
|
@@ -1121,7 +1072,7 @@ describe('Result', () => {
|
|
|
1121
1072
|
|
|
1122
1073
|
describe('genAsync', () => {
|
|
1123
1074
|
it('returns Ok for successful async execution', async () => {
|
|
1124
|
-
const result = await genAsync(async function*($) {
|
|
1075
|
+
const result = await genAsync(async function* ($) {
|
|
1125
1076
|
const a = yield* $(ok(10));
|
|
1126
1077
|
const b = yield* $(await fromPromise(Promise.resolve(5)));
|
|
1127
1078
|
return a + b;
|
|
@@ -1131,7 +1082,7 @@ describe('Result', () => {
|
|
|
1131
1082
|
});
|
|
1132
1083
|
|
|
1133
1084
|
it('short-circuits on first Err', async () => {
|
|
1134
|
-
const result = await genAsync(async function*($) {
|
|
1085
|
+
const result = await genAsync(async function* ($) {
|
|
1135
1086
|
const a = yield* $(ok(10));
|
|
1136
1087
|
const b = yield* $(err('async fail') as Result<number, string>);
|
|
1137
1088
|
return a + b;
|
|
@@ -1141,7 +1092,7 @@ describe('Result', () => {
|
|
|
1141
1092
|
});
|
|
1142
1093
|
|
|
1143
1094
|
it('handles rejected promises via fromPromise', async () => {
|
|
1144
|
-
const result = await genAsync(async function*($) {
|
|
1095
|
+
const result = await genAsync(async function* ($) {
|
|
1145
1096
|
const a = yield* $(await fromPromise(Promise.resolve(10)));
|
|
1146
1097
|
const b = yield* $(await fromPromise(Promise.reject('boom')));
|
|
1147
1098
|
return a + b;
|
|
@@ -1152,7 +1103,7 @@ describe('Result', () => {
|
|
|
1152
1103
|
|
|
1153
1104
|
it('runs try/finally cleanup when short-circuiting on Err', async () => {
|
|
1154
1105
|
let cleanedUp = false;
|
|
1155
|
-
const result = await genAsync(async function*($) {
|
|
1106
|
+
const result = await genAsync(async function* ($) {
|
|
1156
1107
|
try {
|
|
1157
1108
|
const a = yield* $(ok(10));
|
|
1158
1109
|
const b = yield* $(err('async fail') as Result<number, string>);
|
|
@@ -1166,6 +1117,4 @@ describe('Result', () => {
|
|
|
1166
1117
|
expect(cleanedUp).toBe(true);
|
|
1167
1118
|
});
|
|
1168
1119
|
});
|
|
1169
|
-
|
|
1170
1120
|
});
|
|
1171
|
-
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { fromSchema, wrapSchema } from '../schema.js';
|
|
3
|
+
import type { StandardSchema } from '../schema.js';
|
|
4
|
+
import { isOk, isErr } from '../types.js';
|
|
5
|
+
|
|
6
|
+
describe('Schema', () => {
|
|
7
|
+
const validSchema: StandardSchema<string> = {
|
|
8
|
+
'~standard': {
|
|
9
|
+
validate: (value) => (typeof value === 'string' ? { value } : { issues: [{ message: 'Expected string' }] }),
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const asyncSchema: StandardSchema<number> = {
|
|
14
|
+
'~standard': {
|
|
15
|
+
validate: (value) => Promise.resolve(typeof value === 'number' ? { value } : { issues: [{ message: 'Expected number' }] }),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('fromSchema', () => {
|
|
20
|
+
it('returns Ok for valid sync schema', () => {
|
|
21
|
+
const result = fromSchema(validSchema, 'hello');
|
|
22
|
+
expect(isOk(result)).toBe(true);
|
|
23
|
+
expect(result).toBe('hello');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns Err with issues for invalid sync schema', () => {
|
|
27
|
+
const result = fromSchema(validSchema, 42);
|
|
28
|
+
expect(isErr(result)).toBe(true);
|
|
29
|
+
expect((result as any).error).toEqual([{ message: 'Expected string' }]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('returns Ok for valid async schema', async () => {
|
|
33
|
+
const result = await fromSchema(asyncSchema, 42);
|
|
34
|
+
expect(isOk(result)).toBe(true);
|
|
35
|
+
expect(result).toBe(42);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns Err for invalid async schema', async () => {
|
|
39
|
+
const result = await fromSchema(asyncSchema, 'hello');
|
|
40
|
+
expect(isErr(result)).toBe(true);
|
|
41
|
+
expect((result as any).error).toEqual([{ message: 'Expected number' }]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('wrapSchema', () => {
|
|
46
|
+
it('creates a reusable sync validator', () => {
|
|
47
|
+
const parse = wrapSchema(validSchema);
|
|
48
|
+
expect(parse('hello')).toBe('hello');
|
|
49
|
+
expect(isErr(parse(42))).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('creates a reusable async validator', async () => {
|
|
53
|
+
const parse = wrapSchema(asyncSchema);
|
|
54
|
+
expect(await parse(42)).toBe(42);
|
|
55
|
+
expect(isErr(await parse('hello'))).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
|
|
6
|
+
const HELP = `nalloc-codemod - migrate a project from neverthrow to nalloc
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
nalloc-codemod <paths...> [--report <file>] [--dry]
|
|
10
|
+
|
|
11
|
+
Arguments:
|
|
12
|
+
paths Files or directories to migrate (.ts/.tsx/.mts/.cts, .d.ts and node_modules skipped)
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--report <file> Write a markdown report of sites needing manual review
|
|
16
|
+
--dry Analyze and report without writing any files
|
|
17
|
+
--help Show this help
|
|
18
|
+
|
|
19
|
+
Migrate the whole project in one run: converted call sites assume their Result
|
|
20
|
+
values are nalloc values, which only holds once constructors are converted too.
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
const SOURCE_EXT = /\.(ts|tsx|mts|cts)$/;
|
|
24
|
+
|
|
25
|
+
function collectFiles(path: string, files: string[]): void {
|
|
26
|
+
const stats = statSync(path);
|
|
27
|
+
if (stats.isDirectory()) {
|
|
28
|
+
for (const entry of readdirSync(path)) {
|
|
29
|
+
if (entry === 'node_modules' || entry.startsWith('.')) continue;
|
|
30
|
+
collectFiles(join(path, entry), files);
|
|
31
|
+
}
|
|
32
|
+
} else if (SOURCE_EXT.test(path) && !path.endsWith('.d.ts')) {
|
|
33
|
+
files.push(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const paths: string[] = [];
|
|
38
|
+
let reportPath: string | undefined;
|
|
39
|
+
let dry = false;
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
for (let i = 0; i < args.length; i++) {
|
|
42
|
+
const arg = args[i];
|
|
43
|
+
if (arg === '--help') {
|
|
44
|
+
console.log(HELP);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
} else if (arg === '--dry') {
|
|
47
|
+
dry = true;
|
|
48
|
+
} else if (arg === '--report') {
|
|
49
|
+
reportPath = args[++i];
|
|
50
|
+
if (reportPath === undefined) {
|
|
51
|
+
console.error('--report requires a file path');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
paths.push(arg);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (paths.length === 0) {
|
|
60
|
+
console.log(HELP);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const codemod = await (async () => {
|
|
65
|
+
try {
|
|
66
|
+
return await import('./codemod.js');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if ((error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' && String(error).includes('oxc-parser')) {
|
|
69
|
+
console.error('nalloc-codemod requires the optional peer oxc-parser. Install it first:\n\n npm install -D oxc-parser\n');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
|
|
76
|
+
const files: string[] = [];
|
|
77
|
+
for (const path of paths) {
|
|
78
|
+
collectFiles(path, files);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let filesChanged = 0;
|
|
82
|
+
let converted = 0;
|
|
83
|
+
const skipped = [];
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
const source = readFileSync(file, 'utf8');
|
|
86
|
+
const result = codemod.migrateSource(source, file);
|
|
87
|
+
converted += result.converted;
|
|
88
|
+
skipped.push(...result.skipped);
|
|
89
|
+
if (result.changed) {
|
|
90
|
+
filesChanged++;
|
|
91
|
+
if (!dry) {
|
|
92
|
+
writeFileSync(file, result.output);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const report = { filesChanged, converted, skipped };
|
|
98
|
+
if (reportPath !== undefined) {
|
|
99
|
+
writeFileSync(reportPath, codemod.renderReport(report));
|
|
100
|
+
}
|
|
101
|
+
console.log(
|
|
102
|
+
`${dry ? '[dry run] ' : ''}${files.length} files scanned, ${filesChanged} changed, ${converted} sites converted, ${skipped.length} need manual review${reportPath !== undefined ? ` (see ${reportPath})` : ''}`,
|
|
103
|
+
);
|
|
104
|
+
if (skipped.length > 0 && reportPath === undefined) {
|
|
105
|
+
for (const site of skipped) {
|
|
106
|
+
console.log(` ${site.file}:${site.line} [${site.reason}] ${site.text}`);
|
|
107
|
+
}
|
|
108
|
+
}
|