ts-data-forge 13.0.0 → 14.0.1
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 +4 -3
- package/dist/array/impl/array-utils-creation.d.mts +2 -2
- package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-creation.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +56 -29
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +23 -15
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +126 -28
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +39 -5
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +167 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +87 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts +135 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +41 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -0
- package/dist/array/impl/array-utils-modification.d.mts +45 -18
- package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
- package/dist/array/impl/array-utils-modification.mjs.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.d.mts +3 -3
- package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-slice-clamped.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts +11 -11
- package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slicing.mjs +1 -1
- package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.d.mts +36 -19
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +2 -2
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +2 -124
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +1 -7
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/array/impl/index.d.mts +2 -0
- package/dist/array/impl/index.d.mts.map +1 -1
- package/dist/array/impl/index.mjs +5 -3
- package/dist/array/impl/index.mjs.map +1 -1
- package/dist/collections/imap.d.mts +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +2 -2
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.d.mts +2 -2
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/number/num-arithmetic-types.d.mts +2 -2
- package/dist/number/num-arithmetic-types.d.mts.map +1 -1
- package/dist/object/object.mjs +1 -1
- package/dist/string/str.d.mts +44 -44
- package/dist/string/str.d.mts.map +1 -1
- package/dist/string/str.mjs +17 -17
- package/dist/string/str.mjs.map +1 -1
- package/package.json +4 -5
- package/src/array/impl/array-utils-creation.mts +7 -3
- package/src/array/impl/array-utils-creation.test.mts +10 -8
- package/src/array/impl/array-utils-length-bounded-array-cast.mts +88 -50
- package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +40 -13
- package/src/array/impl/array-utils-length-bounded-array-guard.mts +248 -39
- package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +135 -24
- package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +364 -0
- package/src/array/impl/array-utils-length-bounded-tuple-cast.test.mts +207 -0
- package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +266 -0
- package/src/array/impl/array-utils-length-bounded-tuple-guard.test.mts +441 -0
- package/src/array/impl/array-utils-length-constraint-preservation.test.mts +287 -0
- package/src/array/impl/array-utils-modification.mts +84 -61
- package/src/array/impl/array-utils-modification.test.mts +145 -6
- package/src/array/impl/array-utils-reducing-value.mts +5 -5
- package/src/array/impl/array-utils-shape-invariants.test.mts +284 -0
- package/src/array/impl/array-utils-slice-clamped.mts +2 -1
- package/src/array/impl/array-utils-slicing.mts +65 -43
- package/src/array/impl/array-utils-slicing.test.mts +55 -0
- package/src/array/impl/array-utils-transformation.mts +91 -37
- package/src/array/impl/array-utils-transformation.test.mts +96 -23
- package/src/array/impl/array-utils-validation.mts +2 -162
- package/src/array/impl/array-utils-validation.test.mts +0 -371
- package/src/array/impl/index.mts +2 -0
- package/src/collections/imap.mts +1 -1
- package/src/collections/iset-mapped.mts +7 -3
- package/src/collections/iset.mts +7 -3
- package/src/collections/iset.test.mts +9 -6
- package/src/collections/queue.test.mts +1 -1
- package/src/number/num-arithmetic-types.mts +8 -4
- package/src/number/num.test.mts +2 -2
- package/src/string/str.mts +69 -69
- package/src/string/str.test.mts +40 -40
|
@@ -20,36 +20,75 @@ import {
|
|
|
20
20
|
* example, a value narrowed to `MinLengthArray<3, E>` is assignable to
|
|
21
21
|
* `MinLengthArray<1, E>`.
|
|
22
22
|
*
|
|
23
|
+
* The length argument comes first, matching the type-parameter order of
|
|
24
|
+
* `MinLengthArray<MinLength, Elm>`. Omitting the array returns a reusable type
|
|
25
|
+
* guard, which is convenient for `filter`/`pipe`.
|
|
26
|
+
*
|
|
23
27
|
* @example
|
|
24
28
|
*
|
|
25
29
|
* ```ts
|
|
26
|
-
* const input: readonly number[] = [0, 1, 2, 3];
|
|
30
|
+
* const input: readonly number[] = [0, 1, 2, 3] as const;
|
|
27
31
|
*
|
|
28
|
-
* assert.isTrue(Arr.isMinLengthArray(
|
|
32
|
+
* assert.isTrue(Arr.isMinLengthArray(3, input));
|
|
29
33
|
*
|
|
30
|
-
* assert.isFalse(Arr.isMinLengthArray([0]
|
|
34
|
+
* assert.isFalse(Arr.isMinLengthArray(3, [0]));
|
|
31
35
|
*
|
|
32
|
-
* if (Arr.isMinLengthArray(
|
|
36
|
+
* if (Arr.isMinLengthArray(3, input)) {
|
|
33
37
|
* const history: MinLengthArray<3, number> = input;
|
|
34
38
|
*
|
|
35
39
|
* const nonEmpty: MinLengthArray<1, number> = input; // OK (3 >= 1)
|
|
40
|
+
*
|
|
41
|
+
* assert.deepStrictEqual(history[0], 0);
|
|
42
|
+
*
|
|
43
|
+
* assert.deepStrictEqual(nonEmpty[0], 0);
|
|
36
44
|
* }
|
|
45
|
+
*
|
|
46
|
+
* // curried version
|
|
47
|
+
* const hasThree = Arr.isMinLengthArray(3);
|
|
48
|
+
*
|
|
49
|
+
* assert.isTrue(hasThree(input));
|
|
37
50
|
* ```
|
|
38
51
|
*
|
|
39
|
-
* @template Xs - The input array type (tuple types are preserved).
|
|
40
52
|
* @template MinLength - The minimum number of elements (inclusive).
|
|
41
|
-
* @
|
|
53
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
42
54
|
* @param minLength - The minimum number of elements (inclusive).
|
|
55
|
+
* @param xs - The array to check.
|
|
43
56
|
* @returns `true` if `xs.length >= minLength`, `false` otherwise. When `true`,
|
|
44
57
|
* TypeScript narrows `xs` to `MinLengthArray<MinLength, Xs[number]> & Xs`.
|
|
45
58
|
*/
|
|
46
|
-
export
|
|
47
|
-
Xs extends readonly unknown[],
|
|
59
|
+
export function isMinLengthArray<
|
|
48
60
|
MinLength extends SupportedLength,
|
|
61
|
+
Xs extends readonly unknown[],
|
|
49
62
|
>(
|
|
63
|
+
minLength: MinLength,
|
|
50
64
|
xs: Xs,
|
|
65
|
+
): xs is MinLengthArray<MinLength, Xs[number]> & Xs;
|
|
66
|
+
|
|
67
|
+
// Curried version
|
|
68
|
+
export function isMinLengthArray<MinLength extends SupportedLength>(
|
|
51
69
|
minLength: MinLength,
|
|
52
|
-
):
|
|
70
|
+
): <Xs extends readonly unknown[]>(
|
|
71
|
+
xs: Xs,
|
|
72
|
+
) => xs is MinLengthArray<MinLength, Xs[number]> & Xs;
|
|
73
|
+
|
|
74
|
+
export function isMinLengthArray(
|
|
75
|
+
...args:
|
|
76
|
+
| readonly [minLength: SupportedLength, xs: readonly unknown[]]
|
|
77
|
+
| readonly [minLength: SupportedLength]
|
|
78
|
+
): boolean | ((xs: readonly unknown[]) => boolean) {
|
|
79
|
+
switch (args.length) {
|
|
80
|
+
case 2:
|
|
81
|
+
return hasMinLength(...args);
|
|
82
|
+
|
|
83
|
+
case 1:
|
|
84
|
+
return (xs) => hasMinLength(args[0], xs);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const hasMinLength = (
|
|
89
|
+
minLength: SupportedLength,
|
|
90
|
+
xs: readonly unknown[],
|
|
91
|
+
): boolean => xs.length >= minLength;
|
|
53
92
|
|
|
54
93
|
/**
|
|
55
94
|
* Type guard that checks if an array has at most `maxLength` elements.
|
|
@@ -65,36 +104,75 @@ export const isMinLengthArray = <
|
|
|
65
104
|
* example, a value narrowed to `MaxLengthArray<8, E>` is assignable to
|
|
66
105
|
* `MaxLengthArray<16, E>`.
|
|
67
106
|
*
|
|
107
|
+
* The length argument comes first, matching the type-parameter order of
|
|
108
|
+
* `MaxLengthArray<MaxLength, Elm>`. Omitting the array returns a reusable type
|
|
109
|
+
* guard, which is convenient for `filter`/`pipe`.
|
|
110
|
+
*
|
|
68
111
|
* @example
|
|
69
112
|
*
|
|
70
113
|
* ```ts
|
|
71
|
-
* const input: readonly string[] = ['a', 'b', 'c'];
|
|
114
|
+
* const input: readonly string[] = ['a', 'b', 'c'] as const;
|
|
72
115
|
*
|
|
73
|
-
* assert.isTrue(Arr.isMaxLengthArray(
|
|
116
|
+
* assert.isTrue(Arr.isMaxLengthArray(8, input));
|
|
74
117
|
*
|
|
75
|
-
* assert.isFalse(Arr.isMaxLengthArray(
|
|
118
|
+
* assert.isFalse(Arr.isMaxLengthArray(2, input));
|
|
76
119
|
*
|
|
77
|
-
* if (Arr.isMaxLengthArray(
|
|
120
|
+
* if (Arr.isMaxLengthArray(8, input)) {
|
|
78
121
|
* const tags: MaxLengthArray<8, string> = input;
|
|
79
122
|
*
|
|
80
123
|
* const relaxed: MaxLengthArray<16, string> = input; // OK (8 <= 16)
|
|
124
|
+
*
|
|
125
|
+
* assert.deepStrictEqual(tags.length, 3);
|
|
126
|
+
*
|
|
127
|
+
* assert.deepStrictEqual(relaxed.length, 3);
|
|
81
128
|
* }
|
|
129
|
+
*
|
|
130
|
+
* // curried version
|
|
131
|
+
* const fitsInEight = Arr.isMaxLengthArray(8);
|
|
132
|
+
*
|
|
133
|
+
* assert.isTrue(fitsInEight(input));
|
|
82
134
|
* ```
|
|
83
135
|
*
|
|
84
|
-
* @template Xs - The input array type (tuple types are preserved).
|
|
85
136
|
* @template MaxLength - The maximum number of elements (inclusive).
|
|
86
|
-
* @
|
|
137
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
87
138
|
* @param maxLength - The maximum number of elements (inclusive).
|
|
139
|
+
* @param xs - The array to check.
|
|
88
140
|
* @returns `true` if `xs.length <= maxLength`, `false` otherwise. When `true`,
|
|
89
141
|
* TypeScript narrows `xs` to `MaxLengthArray<MaxLength, Xs[number]> & Xs`.
|
|
90
142
|
*/
|
|
91
|
-
export
|
|
92
|
-
Xs extends readonly unknown[],
|
|
143
|
+
export function isMaxLengthArray<
|
|
93
144
|
MaxLength extends SupportedLength,
|
|
145
|
+
Xs extends readonly unknown[],
|
|
94
146
|
>(
|
|
147
|
+
maxLength: MaxLength,
|
|
95
148
|
xs: Xs,
|
|
149
|
+
): xs is MaxLengthArray<MaxLength, Xs[number]> & Xs;
|
|
150
|
+
|
|
151
|
+
// Curried version
|
|
152
|
+
export function isMaxLengthArray<MaxLength extends SupportedLength>(
|
|
96
153
|
maxLength: MaxLength,
|
|
97
|
-
):
|
|
154
|
+
): <Xs extends readonly unknown[]>(
|
|
155
|
+
xs: Xs,
|
|
156
|
+
) => xs is MaxLengthArray<MaxLength, Xs[number]> & Xs;
|
|
157
|
+
|
|
158
|
+
export function isMaxLengthArray(
|
|
159
|
+
...args:
|
|
160
|
+
| readonly [maxLength: SupportedLength, xs: readonly unknown[]]
|
|
161
|
+
| readonly [maxLength: SupportedLength]
|
|
162
|
+
): boolean | ((xs: readonly unknown[]) => boolean) {
|
|
163
|
+
switch (args.length) {
|
|
164
|
+
case 2:
|
|
165
|
+
return hasMaxLength(...args);
|
|
166
|
+
|
|
167
|
+
case 1:
|
|
168
|
+
return (xs) => hasMaxLength(args[0], xs);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const hasMaxLength = (
|
|
173
|
+
maxLength: SupportedLength,
|
|
174
|
+
xs: readonly unknown[],
|
|
175
|
+
): boolean => xs.length <= maxLength;
|
|
98
176
|
|
|
99
177
|
/**
|
|
100
178
|
* Type guard that checks if an array's length is within the inclusive range
|
|
@@ -111,42 +189,89 @@ export const isMaxLengthArray = <
|
|
|
111
189
|
* type checker even when the element type is large — unlike
|
|
112
190
|
* `isBoundedLengthTuple`, which narrows to a union of tuple types.
|
|
113
191
|
*
|
|
192
|
+
* The length arguments come first, matching the type-parameter order of
|
|
193
|
+
* `BoundedLengthArray<MinLength, MaxLength, Elm>`. Omitting the array returns a
|
|
194
|
+
* reusable type guard, which is convenient for `filter`/`pipe`.
|
|
195
|
+
*
|
|
114
196
|
* @example
|
|
115
197
|
*
|
|
116
198
|
* ```ts
|
|
117
|
-
* const input: readonly number[] = [1, 2, 3];
|
|
199
|
+
* const input: readonly number[] = [1, 2, 3] as const;
|
|
118
200
|
*
|
|
119
|
-
* assert.isTrue(Arr.isBoundedLengthArray(
|
|
201
|
+
* assert.isTrue(Arr.isBoundedLengthArray(1, 5, input));
|
|
120
202
|
*
|
|
121
|
-
* assert.isFalse(Arr.isBoundedLengthArray(
|
|
203
|
+
* assert.isFalse(Arr.isBoundedLengthArray(1, 5, []));
|
|
122
204
|
*
|
|
123
|
-
* if (Arr.isBoundedLengthArray(
|
|
205
|
+
* if (Arr.isBoundedLengthArray(1, 5, input)) {
|
|
124
206
|
* const selection: BoundedLengthArray<1, 5, number> = input;
|
|
125
207
|
*
|
|
126
208
|
* const relaxed: BoundedLengthArray<0, 100, number> = input; // OK ([1, 5] ⊆ [0, 100])
|
|
209
|
+
*
|
|
210
|
+
* assert.deepStrictEqual(selection[0], 1);
|
|
211
|
+
*
|
|
212
|
+
* assert.deepStrictEqual(relaxed.length, 3);
|
|
127
213
|
* }
|
|
214
|
+
*
|
|
215
|
+
* // curried version
|
|
216
|
+
* const isSmallSelection = Arr.isBoundedLengthArray(1, 5);
|
|
217
|
+
*
|
|
218
|
+
* assert.isTrue(isSmallSelection(input));
|
|
128
219
|
* ```
|
|
129
220
|
*
|
|
130
|
-
* @template Xs - The input array type (tuple types are preserved).
|
|
131
221
|
* @template MinLength - The minimum number of elements (inclusive).
|
|
132
222
|
* @template MaxLength - The maximum number of elements (inclusive).
|
|
133
|
-
* @
|
|
223
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
134
224
|
* @param minLength - The minimum number of elements (inclusive).
|
|
135
225
|
* @param maxLength - The maximum number of elements (inclusive).
|
|
226
|
+
* @param xs - The array to check.
|
|
136
227
|
* @returns `true` if `minLength <= xs.length && xs.length <= maxLength`,
|
|
137
228
|
* `false` otherwise. When `true`, TypeScript narrows `xs` to
|
|
138
229
|
* `BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs`.
|
|
139
230
|
*/
|
|
140
|
-
export
|
|
141
|
-
Xs extends readonly unknown[],
|
|
231
|
+
export function isBoundedLengthArray<
|
|
142
232
|
MinLength extends SupportedLength,
|
|
143
233
|
MaxLength extends SupportedLength,
|
|
234
|
+
Xs extends readonly unknown[],
|
|
144
235
|
>(
|
|
236
|
+
minLength: MinLength,
|
|
237
|
+
maxLength: MaxLength,
|
|
145
238
|
xs: Xs,
|
|
239
|
+
): xs is BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs;
|
|
240
|
+
|
|
241
|
+
// Curried version
|
|
242
|
+
export function isBoundedLengthArray<
|
|
243
|
+
MinLength extends SupportedLength,
|
|
244
|
+
MaxLength extends SupportedLength,
|
|
245
|
+
>(
|
|
146
246
|
minLength: MinLength,
|
|
147
247
|
maxLength: MaxLength,
|
|
148
|
-
):
|
|
149
|
-
xs
|
|
248
|
+
): <Xs extends readonly unknown[]>(
|
|
249
|
+
xs: Xs,
|
|
250
|
+
) => xs is BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs;
|
|
251
|
+
|
|
252
|
+
export function isBoundedLengthArray(
|
|
253
|
+
...args:
|
|
254
|
+
| readonly [
|
|
255
|
+
minLength: SupportedLength,
|
|
256
|
+
maxLength: SupportedLength,
|
|
257
|
+
xs: readonly unknown[],
|
|
258
|
+
]
|
|
259
|
+
| readonly [minLength: SupportedLength, maxLength: SupportedLength]
|
|
260
|
+
): boolean | ((xs: readonly unknown[]) => boolean) {
|
|
261
|
+
switch (args.length) {
|
|
262
|
+
case 3:
|
|
263
|
+
return hasBoundedLength(...args);
|
|
264
|
+
|
|
265
|
+
case 2:
|
|
266
|
+
return (xs) => hasBoundedLength(args[0], args[1], xs);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const hasBoundedLength = (
|
|
271
|
+
minLength: SupportedLength,
|
|
272
|
+
maxLength: SupportedLength,
|
|
273
|
+
xs: readonly unknown[],
|
|
274
|
+
): boolean => xs.length >= minLength && xs.length <= maxLength;
|
|
150
275
|
|
|
151
276
|
/**
|
|
152
277
|
* Type guard that checks if an array has exactly `length` elements.
|
|
@@ -163,33 +288,117 @@ export const isBoundedLengthArray = <
|
|
|
163
288
|
* type checker even when the element type is large — unlike
|
|
164
289
|
* `isFixedLengthTuple`, which narrows to a structural tuple type.
|
|
165
290
|
*
|
|
291
|
+
* The length argument comes first, matching the type-parameter order of
|
|
292
|
+
* `FixedLengthArray<Length, Elm>`. Omitting the array returns a reusable type
|
|
293
|
+
* guard, which is convenient for `filter`/`pipe`.
|
|
294
|
+
*
|
|
166
295
|
* @example
|
|
167
296
|
*
|
|
168
297
|
* ```ts
|
|
169
|
-
* const input: readonly number[] = [255, 128, 0];
|
|
298
|
+
* const input: readonly number[] = [255, 128, 0] as const;
|
|
170
299
|
*
|
|
171
|
-
* assert.isTrue(Arr.isFixedLengthArray(
|
|
300
|
+
* assert.isTrue(Arr.isFixedLengthArray(3, input));
|
|
172
301
|
*
|
|
173
|
-
* assert.isFalse(Arr.isFixedLengthArray(
|
|
302
|
+
* assert.isFalse(Arr.isFixedLengthArray(4, input));
|
|
174
303
|
*
|
|
175
|
-
* if (Arr.isFixedLengthArray(
|
|
304
|
+
* if (Arr.isFixedLengthArray(3, input)) {
|
|
176
305
|
* const rgb: FixedLengthArray<3, number> = input;
|
|
177
306
|
*
|
|
178
307
|
* const atMost5: MaxLengthArray<5, number> = input; // OK (3 <= 5)
|
|
308
|
+
*
|
|
309
|
+
* assert.deepStrictEqual(rgb[0], 255);
|
|
310
|
+
*
|
|
311
|
+
* assert.deepStrictEqual(atMost5.length, 3);
|
|
179
312
|
* }
|
|
313
|
+
*
|
|
314
|
+
* // curried version
|
|
315
|
+
* const isRgb = Arr.isFixedLengthArray(3);
|
|
316
|
+
*
|
|
317
|
+
* assert.isTrue(isRgb(input));
|
|
180
318
|
* ```
|
|
181
319
|
*
|
|
182
|
-
* @template Xs - The input array type (tuple types are preserved).
|
|
183
320
|
* @template Length - The exact number of elements.
|
|
184
|
-
* @
|
|
321
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
185
322
|
* @param length - The exact number of elements.
|
|
323
|
+
* @param xs - The array to check.
|
|
186
324
|
* @returns `true` if `xs.length === length`, `false` otherwise. When `true`,
|
|
187
325
|
* TypeScript narrows `xs` to `FixedLengthArray<Length, Xs[number]> & Xs`.
|
|
188
326
|
*/
|
|
189
|
-
export
|
|
190
|
-
Xs extends readonly unknown[],
|
|
327
|
+
export function isFixedLengthArray<
|
|
191
328
|
Length extends SupportedLength,
|
|
192
|
-
|
|
193
|
-
|
|
329
|
+
Xs extends readonly unknown[],
|
|
330
|
+
>(length: Length, xs: Xs): xs is FixedLengthArray<Length, Xs[number]> & Xs;
|
|
331
|
+
|
|
332
|
+
// Curried version
|
|
333
|
+
export function isFixedLengthArray<Length extends SupportedLength>(
|
|
194
334
|
length: Length,
|
|
195
|
-
):
|
|
335
|
+
): <Xs extends readonly unknown[]>(
|
|
336
|
+
xs: Xs,
|
|
337
|
+
) => xs is FixedLengthArray<Length, Xs[number]> & Xs;
|
|
338
|
+
|
|
339
|
+
export function isFixedLengthArray(
|
|
340
|
+
...args:
|
|
341
|
+
| readonly [length: SupportedLength, xs: readonly unknown[]]
|
|
342
|
+
| readonly [length: SupportedLength]
|
|
343
|
+
): boolean | ((xs: readonly unknown[]) => boolean) {
|
|
344
|
+
switch (args.length) {
|
|
345
|
+
case 2:
|
|
346
|
+
return hasFixedLength(...args);
|
|
347
|
+
|
|
348
|
+
case 1:
|
|
349
|
+
return (xs) => hasFixedLength(args[0], xs);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const hasFixedLength = (
|
|
354
|
+
length: SupportedLength,
|
|
355
|
+
xs: readonly unknown[],
|
|
356
|
+
): boolean => xs.length === length;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Type guard that checks if an array is empty.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
*
|
|
363
|
+
* ```ts
|
|
364
|
+
* const emptyNumbers: readonly number[] = [] as const;
|
|
365
|
+
*
|
|
366
|
+
* const words = ['Ada', 'Lovelace'] as const;
|
|
367
|
+
*
|
|
368
|
+
* assert.isTrue(Arr.isEmpty(emptyNumbers));
|
|
369
|
+
*
|
|
370
|
+
* assert.isFalse(Arr.isEmpty(words));
|
|
371
|
+
*
|
|
372
|
+
* if (Arr.isEmpty(emptyNumbers)) {
|
|
373
|
+
* // `isEmpty` narrows to the branded `FixedLengthArray<0, number>`, so the
|
|
374
|
+
* // expected value is annotated with the unbranded type.
|
|
375
|
+
* assert.deepStrictEqual<readonly number[]>(emptyNumbers, []);
|
|
376
|
+
* }
|
|
377
|
+
* ```
|
|
378
|
+
*/
|
|
379
|
+
export const isEmpty = <Xs extends readonly unknown[]>(
|
|
380
|
+
array: Xs,
|
|
381
|
+
): array is FixedLengthArray<0, Xs[number]> & Xs => array.length === 0;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Type guard that checks if an array is non-empty.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
*
|
|
388
|
+
* ```ts
|
|
389
|
+
* const users: readonly Readonly<{ id: number }>[] = [{ id: 1 }] as const;
|
|
390
|
+
*
|
|
391
|
+
* const emptyUsers: readonly Readonly<{ id: number }>[] = [] as const;
|
|
392
|
+
*
|
|
393
|
+
* assert.isTrue(Arr.isNonEmpty(users));
|
|
394
|
+
*
|
|
395
|
+
* assert.isFalse(Arr.isNonEmpty(emptyUsers));
|
|
396
|
+
*
|
|
397
|
+
* if (Arr.isNonEmpty(users)) {
|
|
398
|
+
* assert.deepStrictEqual(users[0], { id: 1 });
|
|
399
|
+
* }
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
export const isNonEmpty = <Xs extends readonly unknown[]>(
|
|
403
|
+
array: Xs,
|
|
404
|
+
): array is MinLengthArray<1, Xs[number]> & Xs => array.length > 0;
|
|
@@ -7,30 +7,32 @@ import {
|
|
|
7
7
|
import { expectType } from '../../expect-type.mjs';
|
|
8
8
|
import {
|
|
9
9
|
isBoundedLengthArray,
|
|
10
|
+
isEmpty,
|
|
10
11
|
isFixedLengthArray,
|
|
11
12
|
isMaxLengthArray,
|
|
12
13
|
isMinLengthArray,
|
|
14
|
+
isNonEmpty,
|
|
13
15
|
} from './array-utils-length-bounded-array-guard.mjs';
|
|
14
16
|
|
|
15
17
|
describe(isMinLengthArray, () => {
|
|
16
18
|
test('should return true when the array is long enough', () => {
|
|
17
|
-
assert.isTrue(isMinLengthArray([1, 2, 3]
|
|
19
|
+
assert.isTrue(isMinLengthArray(3, [1, 2, 3]));
|
|
18
20
|
|
|
19
|
-
assert.isTrue(isMinLengthArray([1, 2, 3]
|
|
21
|
+
assert.isTrue(isMinLengthArray(2, [1, 2, 3]));
|
|
20
22
|
|
|
21
|
-
assert.isTrue(isMinLengthArray([]
|
|
23
|
+
assert.isTrue(isMinLengthArray(0, []));
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
test('should return false when the array is too short', () => {
|
|
25
|
-
assert.isFalse(isMinLengthArray([1, 2]
|
|
27
|
+
assert.isFalse(isMinLengthArray(3, [1, 2]));
|
|
26
28
|
|
|
27
|
-
assert.isFalse(isMinLengthArray([]
|
|
29
|
+
assert.isFalse(isMinLengthArray(1, []));
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
test('should act as a type guard', () => {
|
|
31
33
|
const value: readonly number[] = [0, 1, 2, 3] as const;
|
|
32
34
|
|
|
33
|
-
if (isMinLengthArray(
|
|
35
|
+
if (isMinLengthArray(3, value)) {
|
|
34
36
|
expectType<typeof value, MinLengthArray<3, number>>('<=');
|
|
35
37
|
|
|
36
38
|
expectType<typeof value, MinLengthArray<1, number>>('<='); // 3 >= 1
|
|
@@ -58,7 +60,7 @@ describe(isMinLengthArray, () => {
|
|
|
58
60
|
test('should preserve the original array type', () => {
|
|
59
61
|
const tuple = [1, 2, 3] as const;
|
|
60
62
|
|
|
61
|
-
if (isMinLengthArray(
|
|
63
|
+
if (isMinLengthArray(2, tuple)) {
|
|
62
64
|
expectType<typeof tuple, readonly [1, 2, 3]>('<=');
|
|
63
65
|
|
|
64
66
|
expectType<typeof tuple, MinLengthArray<2, 1 | 2 | 3>>('<=');
|
|
@@ -68,23 +70,23 @@ describe(isMinLengthArray, () => {
|
|
|
68
70
|
|
|
69
71
|
describe(isMaxLengthArray, () => {
|
|
70
72
|
test('should return true when the array is short enough', () => {
|
|
71
|
-
assert.isTrue(isMaxLengthArray([1, 2, 3]
|
|
73
|
+
assert.isTrue(isMaxLengthArray(3, [1, 2, 3]));
|
|
72
74
|
|
|
73
|
-
assert.isTrue(isMaxLengthArray([1, 2, 3]
|
|
75
|
+
assert.isTrue(isMaxLengthArray(4, [1, 2, 3]));
|
|
74
76
|
|
|
75
|
-
assert.isTrue(isMaxLengthArray([]
|
|
77
|
+
assert.isTrue(isMaxLengthArray(0, []));
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
test('should return false when the array is too long', () => {
|
|
79
|
-
assert.isFalse(isMaxLengthArray([1, 2, 3]
|
|
81
|
+
assert.isFalse(isMaxLengthArray(2, [1, 2, 3]));
|
|
80
82
|
|
|
81
|
-
assert.isFalse(isMaxLengthArray([1]
|
|
83
|
+
assert.isFalse(isMaxLengthArray(0, [1]));
|
|
82
84
|
});
|
|
83
85
|
|
|
84
86
|
test('should act as a type guard', () => {
|
|
85
87
|
const value: readonly number[] = [0, 1, 2] as const;
|
|
86
88
|
|
|
87
|
-
if (isMaxLengthArray(
|
|
89
|
+
if (isMaxLengthArray(8, value)) {
|
|
88
90
|
expectType<typeof value, MaxLengthArray<8, number>>('<=');
|
|
89
91
|
|
|
90
92
|
expectType<typeof value, MaxLengthArray<16, number>>('<='); // 8 <= 16
|
|
@@ -98,23 +100,23 @@ describe(isMaxLengthArray, () => {
|
|
|
98
100
|
|
|
99
101
|
describe(isBoundedLengthArray, () => {
|
|
100
102
|
test('should return true when the length is within the range', () => {
|
|
101
|
-
assert.isTrue(isBoundedLengthArray([1, 2, 3]
|
|
103
|
+
assert.isTrue(isBoundedLengthArray(1, 5, [1, 2, 3]));
|
|
102
104
|
|
|
103
|
-
assert.isTrue(isBoundedLengthArray(
|
|
105
|
+
assert.isTrue(isBoundedLengthArray(1, 5, [1]));
|
|
104
106
|
|
|
105
|
-
assert.isTrue(isBoundedLengthArray([1, 2, 3, 4, 5]
|
|
107
|
+
assert.isTrue(isBoundedLengthArray(1, 5, [1, 2, 3, 4, 5]));
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
test('should return false when the length is out of the range', () => {
|
|
109
|
-
assert.isFalse(isBoundedLengthArray(
|
|
111
|
+
assert.isFalse(isBoundedLengthArray(1, 5, []));
|
|
110
112
|
|
|
111
|
-
assert.isFalse(isBoundedLengthArray([1, 2, 3, 4, 5, 6]
|
|
113
|
+
assert.isFalse(isBoundedLengthArray(1, 5, [1, 2, 3, 4, 5, 6]));
|
|
112
114
|
});
|
|
113
115
|
|
|
114
116
|
test('should act as a type guard', () => {
|
|
115
117
|
const value: readonly number[] = [1, 2, 3] as const;
|
|
116
118
|
|
|
117
|
-
if (isBoundedLengthArray(
|
|
119
|
+
if (isBoundedLengthArray(1, 5, value)) {
|
|
118
120
|
expectType<typeof value, BoundedLengthArray<1, 5, number>>('<=');
|
|
119
121
|
|
|
120
122
|
expectType<typeof value, BoundedLengthArray<0, 100, number>>('<=');
|
|
@@ -130,21 +132,21 @@ describe(isBoundedLengthArray, () => {
|
|
|
130
132
|
|
|
131
133
|
describe(isFixedLengthArray, () => {
|
|
132
134
|
test('should return true when the length matches exactly', () => {
|
|
133
|
-
assert.isTrue(isFixedLengthArray([1, 2, 3]
|
|
135
|
+
assert.isTrue(isFixedLengthArray(3, [1, 2, 3]));
|
|
134
136
|
|
|
135
|
-
assert.isTrue(isFixedLengthArray([]
|
|
137
|
+
assert.isTrue(isFixedLengthArray(0, []));
|
|
136
138
|
});
|
|
137
139
|
|
|
138
140
|
test('should return false when the length does not match', () => {
|
|
139
|
-
assert.isFalse(isFixedLengthArray([1, 2, 3]
|
|
141
|
+
assert.isFalse(isFixedLengthArray(2, [1, 2, 3]));
|
|
140
142
|
|
|
141
|
-
assert.isFalse(isFixedLengthArray([1, 2, 3]
|
|
143
|
+
assert.isFalse(isFixedLengthArray(4, [1, 2, 3]));
|
|
142
144
|
});
|
|
143
145
|
|
|
144
146
|
test('should act as a type guard', () => {
|
|
145
147
|
const value: readonly number[] = [255, 128, 0] as const;
|
|
146
148
|
|
|
147
|
-
if (isFixedLengthArray(
|
|
149
|
+
if (isFixedLengthArray(3, value)) {
|
|
148
150
|
expectType<typeof value, FixedLengthArray<3, number>>('<=');
|
|
149
151
|
|
|
150
152
|
expectType<typeof value, MaxLengthArray<5, number>>('<='); // 3 <= 5
|
|
@@ -167,3 +169,112 @@ describe(isFixedLengthArray, () => {
|
|
|
167
169
|
}
|
|
168
170
|
});
|
|
169
171
|
});
|
|
172
|
+
|
|
173
|
+
describe('curried length guards', () => {
|
|
174
|
+
test('isMinLengthArray narrows when applied later', () => {
|
|
175
|
+
const hasThree = isMinLengthArray(3);
|
|
176
|
+
|
|
177
|
+
assert.isTrue(hasThree([1, 2, 3]));
|
|
178
|
+
|
|
179
|
+
assert.isFalse(hasThree([1, 2]));
|
|
180
|
+
|
|
181
|
+
const value: readonly number[] = [0, 1, 2, 3] as const;
|
|
182
|
+
|
|
183
|
+
assert.isTrue(hasThree(value));
|
|
184
|
+
|
|
185
|
+
if (hasThree(value)) {
|
|
186
|
+
expectType<typeof value, MinLengthArray<3, number>>('<=');
|
|
187
|
+
|
|
188
|
+
const _first = value[0];
|
|
189
|
+
|
|
190
|
+
expectType<typeof _first, number>('=');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('isMaxLengthArray narrows when applied later', () => {
|
|
195
|
+
const fitsInTwo = isMaxLengthArray(2);
|
|
196
|
+
|
|
197
|
+
assert.isTrue(fitsInTwo([1, 2]));
|
|
198
|
+
|
|
199
|
+
assert.isFalse(fitsInTwo([1, 2, 3]));
|
|
200
|
+
|
|
201
|
+
const value: readonly number[] = [1, 2] as const;
|
|
202
|
+
|
|
203
|
+
if (fitsInTwo(value)) {
|
|
204
|
+
expectType<typeof value, MaxLengthArray<2, number>>('<=');
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('isFixedLengthArray narrows when applied later', () => {
|
|
209
|
+
const isRgb = isFixedLengthArray(3);
|
|
210
|
+
|
|
211
|
+
assert.isTrue(isRgb([255, 128, 0]));
|
|
212
|
+
|
|
213
|
+
assert.isFalse(isRgb([255, 128]));
|
|
214
|
+
|
|
215
|
+
const value: readonly number[] = [255, 128, 0] as const;
|
|
216
|
+
|
|
217
|
+
if (isRgb(value)) {
|
|
218
|
+
expectType<typeof value, FixedLengthArray<3, number>>('<=');
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('isBoundedLengthArray narrows when applied later', () => {
|
|
223
|
+
const isSmallSelection = isBoundedLengthArray(1, 3);
|
|
224
|
+
|
|
225
|
+
assert.isTrue(isSmallSelection([1, 2]));
|
|
226
|
+
|
|
227
|
+
assert.isFalse(isSmallSelection([]));
|
|
228
|
+
|
|
229
|
+
assert.isFalse(isSmallSelection([1, 2, 3, 4]));
|
|
230
|
+
|
|
231
|
+
const value: readonly number[] = [1, 2] as const;
|
|
232
|
+
|
|
233
|
+
if (isSmallSelection(value)) {
|
|
234
|
+
expectType<typeof value, BoundedLengthArray<1, 3, number>>('<=');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('the returned guard is reusable across arrays', () => {
|
|
239
|
+
const isNonEmptyEnough = isMinLengthArray(2);
|
|
240
|
+
|
|
241
|
+
assert.deepStrictEqual([[1], [1, 2], [1, 2, 3]].filter(isNonEmptyEnough), [
|
|
242
|
+
[1, 2],
|
|
243
|
+
[1, 2, 3],
|
|
244
|
+
]);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('empty / non-empty guards', () => {
|
|
249
|
+
describe(isEmpty, () => {
|
|
250
|
+
const xs = [1, 2, 3] as const;
|
|
251
|
+
|
|
252
|
+
const result = isEmpty(xs);
|
|
253
|
+
|
|
254
|
+
expectType<typeof result, boolean>('=');
|
|
255
|
+
|
|
256
|
+
test('case 1', () => {
|
|
257
|
+
assert.isFalse(result);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('case 2', () => {
|
|
261
|
+
assert.isTrue(isEmpty([]));
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe(isNonEmpty, () => {
|
|
266
|
+
const xs = [1, 2, 3] as const;
|
|
267
|
+
|
|
268
|
+
const result = isNonEmpty(xs);
|
|
269
|
+
|
|
270
|
+
expectType<typeof result, boolean>('=');
|
|
271
|
+
|
|
272
|
+
test('case 1', () => {
|
|
273
|
+
assert.isTrue(result);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('case 2', () => {
|
|
277
|
+
assert.isFalse(isNonEmpty([]));
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|