ts-data-forge 6.11.0 → 6.12.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/dist/number/num.d.mts +65 -0
- package/dist/number/num.d.mts.map +1 -1
- package/dist/number/num.mjs +77 -0
- package/dist/number/num.mjs.map +1 -1
- package/package.json +1 -1
- package/src/number/num.mts +78 -0
- package/src/number/num.test.mts +59 -1
package/dist/number/num.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Decrement, type Increment, type Index, type Int, type Min, type NaNType, type NegativeIndex, type NonNegativeNumber, type NonZeroNumber, type PositiveNumber, type PositiveSafeIntWithSmallInt, type RelaxedExclude, type SmallInt, type SmallUint } from 'ts-type-forge';
|
|
2
|
+
import { Result } from '../functional/index.mjs';
|
|
2
3
|
import { type SmallPositiveInt } from '../types.mjs';
|
|
3
4
|
/**
|
|
4
5
|
* Namespace providing utility functions for number manipulation and validation.
|
|
@@ -33,6 +34,70 @@ export declare namespace Num {
|
|
|
33
34
|
* @returns The numeric representation of `n`.
|
|
34
35
|
*/
|
|
35
36
|
export const from: (n: unknown) => number;
|
|
37
|
+
/**
|
|
38
|
+
* Safely parses a base-10 integer from a string, returning a {@link Result}
|
|
39
|
+
* that is `Ok<Int>` for valid input and `Err<Error>` otherwise.
|
|
40
|
+
*
|
|
41
|
+
* This is a stricter alternative to both `parseInt` and `Number`:
|
|
42
|
+
*
|
|
43
|
+
* - Unlike `parseInt('12abc', 10)` (which returns `12`), trailing
|
|
44
|
+
* non-numeric characters make the whole input invalid and yield `Err`.
|
|
45
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
46
|
+
* whitespace-only input yields `Err`.
|
|
47
|
+
*
|
|
48
|
+
* The empty-string case is rejected by delegating to `parseInt` (which
|
|
49
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
50
|
+
* garbage case is rejected via `Number`. Valid input is truncated toward
|
|
51
|
+
* zero, so `'12.9'` becomes `12` and `'-3.5'` becomes `-3`.
|
|
52
|
+
*
|
|
53
|
+
* Only base 10 is supported. Use `Result.unwrapOk` (optionally with a
|
|
54
|
+
* `?? Number.NaN` fallback) or `Result.unwrapOkOr` to get a plain number
|
|
55
|
+
* back.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* assert.strictEqual(
|
|
61
|
+
* Result.unwrapOkOr(Num.safeParseInt('123'), Number.NaN),
|
|
62
|
+
* 123,
|
|
63
|
+
* );
|
|
64
|
+
*
|
|
65
|
+
* assert.strictEqual(
|
|
66
|
+
* Result.unwrapOkOr(Num.safeParseInt('12.9'), Number.NaN),
|
|
67
|
+
* 12,
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* assert.strictEqual(
|
|
71
|
+
* Result.unwrapOkOr(Num.safeParseInt('-12.9'), Number.NaN),
|
|
72
|
+
* -12,
|
|
73
|
+
* );
|
|
74
|
+
*
|
|
75
|
+
* assert.strictEqual(Number.parseInt('-12.9', 10), -12);
|
|
76
|
+
*
|
|
77
|
+
* // Native `parseInt` ignores trailing non-numeric characters
|
|
78
|
+
*
|
|
79
|
+
* assert.strictEqual(Number.parseInt('123abc', 10), 123);
|
|
80
|
+
*
|
|
81
|
+
* assert.isTrue(Number.isNaN(Number('123abc')));
|
|
82
|
+
*
|
|
83
|
+
* assert.isTrue(Result.isErr(Num.safeParseInt('123abc')));
|
|
84
|
+
*
|
|
85
|
+
* // Whitespace is not a valid integer, so we return an error instead of coercing to 0.
|
|
86
|
+
*
|
|
87
|
+
* assert.isTrue(Number.isNaN(Number.parseInt(' ', 10)));
|
|
88
|
+
*
|
|
89
|
+
* assert.strictEqual(Number(' '), 0); // Native `Number` coerces whitespace to 0
|
|
90
|
+
*
|
|
91
|
+
* assert.isTrue(Result.isErr(Num.safeParseInt('')));
|
|
92
|
+
*
|
|
93
|
+
* assert.strictEqual(Result.unwrapOk(Num.safeParseInt(' ')), undefined);
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @param s The string to parse.
|
|
97
|
+
* @returns `Result.ok(parsedInt)` for valid input, otherwise `Result.err`
|
|
98
|
+
* wrapping an `Error` describing the invalid input.
|
|
99
|
+
*/
|
|
100
|
+
export const safeParseInt: (s: string) => Result<Int, Error>;
|
|
36
101
|
/**
|
|
37
102
|
* Type guard that checks if a number is non-zero.
|
|
38
103
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"num.d.mts","sourceRoot":"","sources":["../../src/number/num.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,KAAK,EACV,KAAK,GAAG,EACR,KAAK,GAAG,EACR,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,2BAA2B,EAChC,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,SAAS,EAEf,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,yBAAiB,GAAG,CAAC;IACnB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAe,CAAC;IAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,MAAM,CAAC,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,EACxC,KAAK,CAAC,KACL,GAAG,IAAI,aAAa,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,CAAc,CAAC;IAI5D;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,MAAM,CAAC,MAAM,aAAa,GAAI,CAAC,SAAS,MAAM,EAC5C,KAAK,CAAC,KACL,GAAG,IAAI,iBAAiB,GAAG,cAAc,CAAC,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CACzD,CAAC;IAEX;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EACzC,KAAK,CAAC,KACL,GAAG,IAAI,cAAc,GAAG,cAAc,CAAC,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAC3D,CAAC;IAEV;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,MAAM,SAAS,GACnB,YAAY,MAAM,EAAE,YAAY,MAAM,MACtC,GAAG,MAAM,KAAG,OACsB,CAAC;IAEtC;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,MAAM,kBAAkB,GAC5B,YAAY,MAAM,EAAE,YAAY,MAAM,MACtC,GAAG,MAAM,KAAG,OACuB,CAAC;IAEvC;;;;;;;;;OASG;IACH,KAAK,EAAE,GAAG,QAAQ,CAAC;SAAG,CAAC,IAAI,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC;KAAE,CAAC,CAAC;IAEnD;;;;;;;;;OASG;IACH,KAAK,GAAG,GAAG,QAAQ,CAAC;SAAG,CAAC,IAAI,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;KAAE,CAAC,CAAC;IAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,MAAM,CAAC,MAAM,aAAa,GACvB,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,SAAS,EAAE,YAAY,CAAC,EAAE,YAAY,CAAC,MACtE,GAAG,MAAM,KAAG,CAAC,IAAI,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CACY,CAAC;IAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,MAAM,CAAC,MAAM,sBAAsB,GAChC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,SAAS,EAAE,YAAY,CAAC,EAAE,YAAY,CAAC,MACtE,GAAG,MAAM,KAAG,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CACY,CAAC;IAElE;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,UAAU,KAAK,CACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,MAAM,CAAC;IAGV,MAAM,UAAU,KAAK,CACnB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAyB9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,MAAM,GAAG,GAAI,GAAG,MAAM,EAAE,GAAG,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAG,MAE7D,CAAC;IAER;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,MAAM,MAAM,GACjB,GAAG,MAAM,EACT,GAAG,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,KACjC,MAEwC,CAAC;IAE5C;;;;;;;;;OASG;IACH,MAAM,CAAC,MAAM,OAAO,GAClB,KAAK,MAAM,EACX,WAAW,2BAA2B,KACrC,MAKF,CAAC;IAEF;;;;;;;;;OASG;IAEH,MAAM,CAAC,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,GAAmC,CAAC;IAE7E;;;;;;;;;OASG;IACH,MAAM,CAAC,MAAM,KAAK,GAChB,OAAO,2BAA2B,KACjC,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAM1B,CAAC;IAEF;;;;;;;;;;OAUG;IACH,MAAM,CAAC,MAAM,gBAAgB,GAAI,CAAC,SAAS,MAAM,EAC/C,KAAK,CAAC,KACL,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,SAIS,CAAC;IAE1C;;;;;;;;;;OAUG;IACH,MAAM,CAAC,MAAM,SAAS,GAAI,CAAC,SAAS,SAAS,EAAE,GAAG,CAAC,KAAG,SAAS,CAAC,CAAC,CAExC,CAAC;IAE1B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,MAAM,SAAS,GAAI,CAAC,SAAS,gBAAgB,EAAE,GAAG,CAAC,KAAG,SAAS,CAAC,CAAC,CAE/C,CAAC;;CAC3B"}
|
|
1
|
+
{"version":3,"file":"num.d.mts","sourceRoot":"","sources":["../../src/number/num.mts"],"names":[],"mappings":"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"}
|
package/dist/number/num.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { err } from '../functional/result/impl/result-err.mjs';
|
|
2
|
+
import { ok } from '../functional/result/impl/result-ok.mjs';
|
|
3
|
+
import '@sindresorhus/is';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Namespace providing utility functions for number manipulation and validation.
|
|
3
7
|
*
|
|
@@ -32,6 +36,79 @@ var Num;
|
|
|
32
36
|
* @returns The numeric representation of `n`.
|
|
33
37
|
*/
|
|
34
38
|
Num.from = Number;
|
|
39
|
+
/**
|
|
40
|
+
* Safely parses a base-10 integer from a string, returning a {@link Result}
|
|
41
|
+
* that is `Ok<Int>` for valid input and `Err<Error>` otherwise.
|
|
42
|
+
*
|
|
43
|
+
* This is a stricter alternative to both `parseInt` and `Number`:
|
|
44
|
+
*
|
|
45
|
+
* - Unlike `parseInt('12abc', 10)` (which returns `12`), trailing
|
|
46
|
+
* non-numeric characters make the whole input invalid and yield `Err`.
|
|
47
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
48
|
+
* whitespace-only input yields `Err`.
|
|
49
|
+
*
|
|
50
|
+
* The empty-string case is rejected by delegating to `parseInt` (which
|
|
51
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
52
|
+
* garbage case is rejected via `Number`. Valid input is truncated toward
|
|
53
|
+
* zero, so `'12.9'` becomes `12` and `'-3.5'` becomes `-3`.
|
|
54
|
+
*
|
|
55
|
+
* Only base 10 is supported. Use `Result.unwrapOk` (optionally with a
|
|
56
|
+
* `?? Number.NaN` fallback) or `Result.unwrapOkOr` to get a plain number
|
|
57
|
+
* back.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* assert.strictEqual(
|
|
63
|
+
* Result.unwrapOkOr(Num.safeParseInt('123'), Number.NaN),
|
|
64
|
+
* 123,
|
|
65
|
+
* );
|
|
66
|
+
*
|
|
67
|
+
* assert.strictEqual(
|
|
68
|
+
* Result.unwrapOkOr(Num.safeParseInt('12.9'), Number.NaN),
|
|
69
|
+
* 12,
|
|
70
|
+
* );
|
|
71
|
+
*
|
|
72
|
+
* assert.strictEqual(
|
|
73
|
+
* Result.unwrapOkOr(Num.safeParseInt('-12.9'), Number.NaN),
|
|
74
|
+
* -12,
|
|
75
|
+
* );
|
|
76
|
+
*
|
|
77
|
+
* assert.strictEqual(Number.parseInt('-12.9', 10), -12);
|
|
78
|
+
*
|
|
79
|
+
* // Native `parseInt` ignores trailing non-numeric characters
|
|
80
|
+
*
|
|
81
|
+
* assert.strictEqual(Number.parseInt('123abc', 10), 123);
|
|
82
|
+
*
|
|
83
|
+
* assert.isTrue(Number.isNaN(Number('123abc')));
|
|
84
|
+
*
|
|
85
|
+
* assert.isTrue(Result.isErr(Num.safeParseInt('123abc')));
|
|
86
|
+
*
|
|
87
|
+
* // Whitespace is not a valid integer, so we return an error instead of coercing to 0.
|
|
88
|
+
*
|
|
89
|
+
* assert.isTrue(Number.isNaN(Number.parseInt(' ', 10)));
|
|
90
|
+
*
|
|
91
|
+
* assert.strictEqual(Number(' '), 0); // Native `Number` coerces whitespace to 0
|
|
92
|
+
*
|
|
93
|
+
* assert.isTrue(Result.isErr(Num.safeParseInt('')));
|
|
94
|
+
*
|
|
95
|
+
* assert.strictEqual(Result.unwrapOk(Num.safeParseInt(' ')), undefined);
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* @param s The string to parse.
|
|
99
|
+
* @returns `Result.ok(parsedInt)` for valid input, otherwise `Result.err`
|
|
100
|
+
* wrapping an `Error` describing the invalid input.
|
|
101
|
+
*/
|
|
102
|
+
Num.safeParseInt = (s) => {
|
|
103
|
+
const viaNumber = Number(s);
|
|
104
|
+
// `Number('')` / `Number(' ')` は 0 を返すが、`parseInt` は NaN を返す。
|
|
105
|
+
// 末尾不正文字 ('12abc' 等) は `Number` 側が NaN にするので、両者が共に
|
|
106
|
+
// 有効な場合のみ採用することで空文字・空白のみ・末尾不正をまとめて弾く。
|
|
107
|
+
return Number.isNaN(viaNumber) || Number.isNaN(Number.parseInt(s, 10))
|
|
108
|
+
? err(new Error(`safeParseInt: "${s}" is not a valid base-10 integer`))
|
|
109
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
110
|
+
ok(Math.trunc(viaNumber));
|
|
111
|
+
};
|
|
35
112
|
/**
|
|
36
113
|
* Type guard that checks if a number is non-zero.
|
|
37
114
|
*
|
package/dist/number/num.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"num.mjs","sources":["../../src/number/num.mts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"num.mjs","sources":["../../src/number/num.mts"],"sourcesContent":[null],"names":["Result.err","Result.ok"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/number/num.mts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type UnknownBrand,
|
|
17
17
|
} from 'ts-type-forge';
|
|
18
18
|
import { expectType } from '../expect-type.mjs';
|
|
19
|
+
import { Result } from '../functional/index.mjs';
|
|
19
20
|
import { type SmallPositiveInt } from '../types.mjs';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -52,6 +53,83 @@ export namespace Num {
|
|
|
52
53
|
*/
|
|
53
54
|
export const from: (n: unknown) => number = Number;
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Safely parses a base-10 integer from a string, returning a {@link Result}
|
|
58
|
+
* that is `Ok<Int>` for valid input and `Err<Error>` otherwise.
|
|
59
|
+
*
|
|
60
|
+
* This is a stricter alternative to both `parseInt` and `Number`:
|
|
61
|
+
*
|
|
62
|
+
* - Unlike `parseInt('12abc', 10)` (which returns `12`), trailing
|
|
63
|
+
* non-numeric characters make the whole input invalid and yield `Err`.
|
|
64
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
65
|
+
* whitespace-only input yields `Err`.
|
|
66
|
+
*
|
|
67
|
+
* The empty-string case is rejected by delegating to `parseInt` (which
|
|
68
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
69
|
+
* garbage case is rejected via `Number`. Valid input is truncated toward
|
|
70
|
+
* zero, so `'12.9'` becomes `12` and `'-3.5'` becomes `-3`.
|
|
71
|
+
*
|
|
72
|
+
* Only base 10 is supported. Use `Result.unwrapOk` (optionally with a
|
|
73
|
+
* `?? Number.NaN` fallback) or `Result.unwrapOkOr` to get a plain number
|
|
74
|
+
* back.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* assert.strictEqual(
|
|
80
|
+
* Result.unwrapOkOr(Num.safeParseInt('123'), Number.NaN),
|
|
81
|
+
* 123,
|
|
82
|
+
* );
|
|
83
|
+
*
|
|
84
|
+
* assert.strictEqual(
|
|
85
|
+
* Result.unwrapOkOr(Num.safeParseInt('12.9'), Number.NaN),
|
|
86
|
+
* 12,
|
|
87
|
+
* );
|
|
88
|
+
*
|
|
89
|
+
* assert.strictEqual(
|
|
90
|
+
* Result.unwrapOkOr(Num.safeParseInt('-12.9'), Number.NaN),
|
|
91
|
+
* -12,
|
|
92
|
+
* );
|
|
93
|
+
*
|
|
94
|
+
* assert.strictEqual(Number.parseInt('-12.9', 10), -12);
|
|
95
|
+
*
|
|
96
|
+
* // Native `parseInt` ignores trailing non-numeric characters
|
|
97
|
+
*
|
|
98
|
+
* assert.strictEqual(Number.parseInt('123abc', 10), 123);
|
|
99
|
+
*
|
|
100
|
+
* assert.isTrue(Number.isNaN(Number('123abc')));
|
|
101
|
+
*
|
|
102
|
+
* assert.isTrue(Result.isErr(Num.safeParseInt('123abc')));
|
|
103
|
+
*
|
|
104
|
+
* // Whitespace is not a valid integer, so we return an error instead of coercing to 0.
|
|
105
|
+
*
|
|
106
|
+
* assert.isTrue(Number.isNaN(Number.parseInt(' ', 10)));
|
|
107
|
+
*
|
|
108
|
+
* assert.strictEqual(Number(' '), 0); // Native `Number` coerces whitespace to 0
|
|
109
|
+
*
|
|
110
|
+
* assert.isTrue(Result.isErr(Num.safeParseInt('')));
|
|
111
|
+
*
|
|
112
|
+
* assert.strictEqual(Result.unwrapOk(Num.safeParseInt(' ')), undefined);
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @param s The string to parse.
|
|
116
|
+
* @returns `Result.ok(parsedInt)` for valid input, otherwise `Result.err`
|
|
117
|
+
* wrapping an `Error` describing the invalid input.
|
|
118
|
+
*/
|
|
119
|
+
export const safeParseInt = (s: string): Result<Int, Error> => {
|
|
120
|
+
const viaNumber = Number(s);
|
|
121
|
+
|
|
122
|
+
// `Number('')` / `Number(' ')` は 0 を返すが、`parseInt` は NaN を返す。
|
|
123
|
+
// 末尾不正文字 ('12abc' 等) は `Number` 側が NaN にするので、両者が共に
|
|
124
|
+
// 有効な場合のみ採用することで空文字・空白のみ・末尾不正をまとめて弾く。
|
|
125
|
+
return Number.isNaN(viaNumber) || Number.isNaN(Number.parseInt(s, 10))
|
|
126
|
+
? Result.err(
|
|
127
|
+
new Error(`safeParseInt: "${s}" is not a valid base-10 integer`),
|
|
128
|
+
)
|
|
129
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
130
|
+
Result.ok(Math.trunc(viaNumber) as Int);
|
|
131
|
+
};
|
|
132
|
+
|
|
55
133
|
/**
|
|
56
134
|
* Type guard that checks if a number is non-zero.
|
|
57
135
|
*
|
package/src/number/num.test.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type NonZeroNumber } from 'ts-type-forge';
|
|
2
2
|
import { expectType } from '../expect-type.mjs';
|
|
3
|
-
import { pipe } from '../functional/index.mjs';
|
|
3
|
+
import { pipe, Result } from '../functional/index.mjs';
|
|
4
4
|
import { asNonZeroFiniteNumber } from './branded-types/index.mjs';
|
|
5
5
|
import { Num } from './num.mjs';
|
|
6
6
|
|
|
@@ -132,6 +132,64 @@ describe('Num test', () => {
|
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
describe('safeParseInt', () => {
|
|
136
|
+
test('parses valid integer strings into Ok', () => {
|
|
137
|
+
expect(Result.unwrapOk(Num.safeParseInt('123'))).toBe(123);
|
|
138
|
+
|
|
139
|
+
expect(Result.unwrapOk(Num.safeParseInt('-42'))).toBe(-42);
|
|
140
|
+
|
|
141
|
+
expect(Result.unwrapOk(Num.safeParseInt('+7'))).toBe(7);
|
|
142
|
+
|
|
143
|
+
expect(Result.unwrapOk(Num.safeParseInt('0'))).toBe(0);
|
|
144
|
+
|
|
145
|
+
expect(Result.unwrapOk(Num.safeParseInt(' 12 '))).toBe(12);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('truncates non-integer numeric strings toward zero', () => {
|
|
149
|
+
expect(Result.unwrapOk(Num.safeParseInt('12.9'))).toBe(12);
|
|
150
|
+
|
|
151
|
+
expect(Result.unwrapOk(Num.safeParseInt('-3.5'))).toBe(-3);
|
|
152
|
+
|
|
153
|
+
expect(Result.unwrapOk(Num.safeParseInt('1e3'))).toBe(1000);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('rejects trailing non-numeric characters (unlike parseInt)', () => {
|
|
157
|
+
assert.isTrue(Result.isErr(Num.safeParseInt('123abc')));
|
|
158
|
+
|
|
159
|
+
assert.isTrue(Result.isErr(Num.safeParseInt('12px')));
|
|
160
|
+
|
|
161
|
+
assert.isTrue(Result.isErr(Num.safeParseInt('abc')));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('rejects empty / whitespace-only input (unlike Number)', () => {
|
|
165
|
+
assert.isTrue(Result.isErr(Num.safeParseInt('')));
|
|
166
|
+
|
|
167
|
+
assert.isTrue(Result.isErr(Num.safeParseInt(' ')));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('rejects non-finite words', () => {
|
|
171
|
+
assert.isTrue(Result.isErr(Num.safeParseInt('Infinity')));
|
|
172
|
+
|
|
173
|
+
assert.isTrue(Result.isErr(Num.safeParseInt('NaN')));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('the Err carries a descriptive Error', () => {
|
|
177
|
+
const result = Num.safeParseInt('nope');
|
|
178
|
+
|
|
179
|
+
assert.isTrue(Result.isErr(result));
|
|
180
|
+
|
|
181
|
+
if (Result.isErr(result)) {
|
|
182
|
+
expect(Result.unwrapErr(result)).toBeInstanceOf(Error);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('composes with Result.unwrapOk + nullish fallback', () => {
|
|
187
|
+
expect(Result.unwrapOk(Num.safeParseInt('42')) ?? Number.NaN).toBe(42);
|
|
188
|
+
|
|
189
|
+
expect(Result.unwrapOk(Num.safeParseInt('')) ?? Number.NaN).toBeNaN();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
135
193
|
describe('isInRange', () => {
|
|
136
194
|
test('checks range (lower inclusive, upper exclusive)', () => {
|
|
137
195
|
const inRange = Num.isInRange(0, 10);
|