inferred-types 0.24.3 → 0.26.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/index.d.ts +179 -34
- package/dist/index.js +337 -255
- package/dist/index.mjs +332 -252
- package/package.json +2 -1
- package/src/types/TypeInfo/Includes.ts +34 -0
- package/src/types/TypeInfo/IsBooleanLiteral.ts +7 -0
- package/src/types/TypeInfo/IsLiteral.ts +20 -0
- package/src/types/TypeInfo/IsNumericLiteral.ts +7 -0
- package/src/types/TypeInfo/IsStringLiteral.ts +7 -0
- package/src/types/TypeInfo/index.ts +5 -0
- package/src/types/functions/LogicFunction.ts +4 -0
- package/src/types/functions/index.ts +1 -0
- package/src/types/index.ts +1 -1
- package/src/utility/boolean-logic/and.ts +15 -0
- package/src/utility/boolean-logic/filter.ts +268 -0
- package/src/utility/boolean-logic/index.ts +4 -0
- package/src/utility/boolean-logic/not.ts +15 -0
- package/src/utility/boolean-logic/or.ts +15 -0
- package/src/utility/index.ts +1 -1
- package/src/utility/lists/asArray.ts +34 -0
- package/src/utility/lists/index.ts +1 -0
- package/tests/Includes-spec.ts +26 -5
- package/tests/TypeInfo/IsLiteral.spec.ts +49 -0
- package/tests/boolean-logic/boolean.spec.ts +21 -0
- package/tests/boolean-logic/filter.spec.ts +52 -0
- package/tests/lists/asArray.test.ts +91 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +7 -0
- package/src/types/Includes.ts +0 -5
- package/src/utility/map-reduce/filter.ts +0 -35
- package/src/utility/map-reduce/index.ts +0 -12
package/src/types/index.ts
CHANGED
|
@@ -12,7 +12,6 @@ export * from "./First";
|
|
|
12
12
|
export * from "./FunctionType";
|
|
13
13
|
export * from "./If";
|
|
14
14
|
export * from "./Include";
|
|
15
|
-
export * from "./Includes";
|
|
16
15
|
export * from "./KeyedRecord";
|
|
17
16
|
export * from "./Keys";
|
|
18
17
|
export * from "./Length";
|
|
@@ -41,6 +40,7 @@ export * from "./lists/index";
|
|
|
41
40
|
export * from "./string-literals/index";
|
|
42
41
|
export * from "./tuples/index";
|
|
43
42
|
export * from "./type-conversion/index";
|
|
43
|
+
export * from "./TypeInfo/index";
|
|
44
44
|
|
|
45
45
|
// #endregion auto-indexed files
|
|
46
46
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LogicFunction } from "src/types/functions";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Groups a number of "logic functions" together by combining their results using
|
|
5
|
+
* the logical **AND** operator.
|
|
6
|
+
*
|
|
7
|
+
* **Note:** a "logic function" is any function which returns a boolean
|
|
8
|
+
*/
|
|
9
|
+
export const and = <T extends any[]>(...ops: readonly LogicFunction<T>[]): LogicFunction<T> => {
|
|
10
|
+
const fn: LogicFunction<T> = (...args: T) => {
|
|
11
|
+
return [...ops].every((i) => i(...args));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return fn;
|
|
15
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { asArray } from "../lists/asArray";
|
|
2
|
+
|
|
3
|
+
// string filters
|
|
4
|
+
export type FilterStarts = {
|
|
5
|
+
/** one or more string which the value is allowed to start with */
|
|
6
|
+
startsWith: string | string[];
|
|
7
|
+
};
|
|
8
|
+
export type FilterIs = {
|
|
9
|
+
/** whether a string _**is**_ of a particular value */
|
|
10
|
+
is: string | string[];
|
|
11
|
+
};
|
|
12
|
+
export type FilterEnds = { endsWith: string | string[] };
|
|
13
|
+
export type FilterContains = {
|
|
14
|
+
/** whether any of the strings specified are _contained_ in the value */
|
|
15
|
+
contains: string | string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// numeric filters
|
|
19
|
+
export type FilterEquals = {
|
|
20
|
+
/** one or more values which _equal_ the value passed in */
|
|
21
|
+
equals: number | number[];
|
|
22
|
+
};
|
|
23
|
+
export type FilterNotEqual = {
|
|
24
|
+
/** one or more values which ALL _do not equal_ the value passed in */
|
|
25
|
+
notEqual: number | number[];
|
|
26
|
+
};
|
|
27
|
+
export type FilterGreaterThan = {
|
|
28
|
+
/** the incoming value is greater than this value */
|
|
29
|
+
greaterThan: number;
|
|
30
|
+
};
|
|
31
|
+
export type FilterLessThan = {
|
|
32
|
+
/** the incoming value is less than this value */
|
|
33
|
+
lessThan: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type StringFilter =
|
|
37
|
+
| FilterIs
|
|
38
|
+
| FilterStarts
|
|
39
|
+
| FilterEnds
|
|
40
|
+
| FilterContains
|
|
41
|
+
| (FilterStarts & FilterEnds)
|
|
42
|
+
| (FilterStarts & FilterContains)
|
|
43
|
+
| (FilterEnds & FilterContains)
|
|
44
|
+
| (FilterStarts & FilterEnds & FilterContains);
|
|
45
|
+
|
|
46
|
+
export type NumericFilter =
|
|
47
|
+
| FilterEquals
|
|
48
|
+
| FilterNotEqual
|
|
49
|
+
| FilterGreaterThan
|
|
50
|
+
| FilterLessThan
|
|
51
|
+
| (FilterEquals & FilterNotEqual)
|
|
52
|
+
| (FilterEquals & FilterGreaterThan)
|
|
53
|
+
| (FilterEquals & FilterLessThan)
|
|
54
|
+
| (FilterNotEqual & FilterGreaterThan)
|
|
55
|
+
| (FilterNotEqual & FilterLessThan)
|
|
56
|
+
| (FilterGreaterThan & FilterLessThan)
|
|
57
|
+
| (FilterEquals & FilterGreaterThan & FilterLessThan)
|
|
58
|
+
| (FilterNotEqual & FilterGreaterThan & FilterLessThan)
|
|
59
|
+
| (FilterEquals & FilterNotEqual & FilterGreaterThan & FilterLessThan);
|
|
60
|
+
|
|
61
|
+
export type FilterDefn = StringFilter | NumericFilter;
|
|
62
|
+
|
|
63
|
+
// NOT filter
|
|
64
|
+
export type NotFilter = {
|
|
65
|
+
/**
|
|
66
|
+
* **not**
|
|
67
|
+
*
|
|
68
|
+
* If you want to build a filter who's conditions being met results in _filtering_
|
|
69
|
+
* the value rather than accepting it then choose this.
|
|
70
|
+
*/
|
|
71
|
+
not: FilterDefn;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export function isNotFilter(f: FilterDefn | NotFilter): f is NotFilter {
|
|
75
|
+
return typeof f === "object" && "not" in f;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type UnwrapNot<T extends FilterDefn | NotFilter> = T extends NotFilter
|
|
79
|
+
? T["not"] extends StringFilter
|
|
80
|
+
? StringFilter
|
|
81
|
+
: NumericFilter
|
|
82
|
+
: T;
|
|
83
|
+
|
|
84
|
+
export function isNumericFilter(filter: FilterDefn): filter is NumericFilter {
|
|
85
|
+
return "equals" in filter ||
|
|
86
|
+
"notEqual" in filter ||
|
|
87
|
+
"greaterThan" in filter ||
|
|
88
|
+
"lessThan" in filter
|
|
89
|
+
? true
|
|
90
|
+
: false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type UndefinedValue<U extends boolean> = true extends U
|
|
94
|
+
? "undefined treated as 'true'"
|
|
95
|
+
: U extends "no-impact"
|
|
96
|
+
? "undefined treated in no-impact fashion"
|
|
97
|
+
: "undefined treated as 'false'";
|
|
98
|
+
|
|
99
|
+
export type UndefinedTreatment = "undefined treated as 'true'" | "undefined treated as 'false'";
|
|
100
|
+
|
|
101
|
+
export type LogicalCombinator = "AND" | "OR";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* **FilterFn**
|
|
105
|
+
*
|
|
106
|
+
* A filter function derived from the `filter()` configurator. This function is intended to provide a type-strong _filter_ function which can be used like so:
|
|
107
|
+
* be used like:
|
|
108
|
+
* ```ts
|
|
109
|
+
* const onlyPrivate = filter({ startsWith: "_" });
|
|
110
|
+
* const privateFiles = files.filter(onlyPrivate);
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
*/
|
|
114
|
+
export type FilterFn<T extends StringFilter | NumericFilter> = T extends StringFilter
|
|
115
|
+
? <V extends string | undefined>(input: V) => boolean
|
|
116
|
+
: <V extends number | undefined>(input: V) => boolean;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Defines a logical function for each condition type
|
|
120
|
+
*/
|
|
121
|
+
export type ConditionFilter<T extends StringFilter | NumericFilter> = T extends StringFilter
|
|
122
|
+
? (input: string) => boolean
|
|
123
|
+
: (input: number) => boolean;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Wraps numeric filter's configuration into a logic filter
|
|
127
|
+
*/
|
|
128
|
+
const numericOps = <L extends LogicalCombinator>(config: NumericFilter, boolLogic: L) => {
|
|
129
|
+
const equals = (n: number | number[]): [ConditionFilter<NumericFilter>] | [] => {
|
|
130
|
+
const f = asArray(n);
|
|
131
|
+
return f.length === 0 ? [] : [(input?: number) => f.some((i) => i === input)];
|
|
132
|
+
};
|
|
133
|
+
const notEqual = (n: number | number[]): [ConditionFilter<NumericFilter>] | [] => {
|
|
134
|
+
const f = asArray(n);
|
|
135
|
+
return f.length === 0 ? [] : [(input?: number) => f.every((i) => i !== input)];
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** returns 1 FilterFn's */
|
|
139
|
+
const greaterThan = <V extends number>(n: V) => {
|
|
140
|
+
const val = [(input?: V) => input !== undefined && input > n] as [(input: V) => boolean];
|
|
141
|
+
return val as [ConditionFilter<NumericFilter>];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** returns 0 or 1 FilterFn's */
|
|
145
|
+
const lessThan = <V extends number>(n: V) => {
|
|
146
|
+
const val = [(input: V) => input !== undefined && input < n];
|
|
147
|
+
return val as [ConditionFilter<NumericFilter>] | [];
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const conditions = [
|
|
151
|
+
//
|
|
152
|
+
...("equals" in config ? equals(config.equals) : []),
|
|
153
|
+
...("notEqual" in config ? notEqual(config.notEqual) : []),
|
|
154
|
+
...("greaterThan" in config ? greaterThan(config.greaterThan) : []),
|
|
155
|
+
...("lessThan" in config ? lessThan(config.lessThan) : []),
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
const combined =
|
|
159
|
+
boolLogic === "AND"
|
|
160
|
+
? <N extends number>(input: N) => conditions.every((c) => c(input))
|
|
161
|
+
: <N extends number>(input: N) => conditions.some((c) => c(input));
|
|
162
|
+
|
|
163
|
+
return combined;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** wraps configuration along with boolean logic */
|
|
167
|
+
const stringOps = <L extends LogicalCombinator>(
|
|
168
|
+
config: StringFilter,
|
|
169
|
+
boolLogic: L
|
|
170
|
+
): ConditionFilter<StringFilter> => {
|
|
171
|
+
const startsWith = (n: string | string[]): [ConditionFilter<StringFilter>] | [] => {
|
|
172
|
+
const f = asArray(n);
|
|
173
|
+
return f.length === 0 ? [] : [(input?: string) => f.some((i) => input?.startsWith(i))];
|
|
174
|
+
};
|
|
175
|
+
const endsWith = (n: string | string[]): [ConditionFilter<StringFilter>] | [] => {
|
|
176
|
+
const f = asArray(n);
|
|
177
|
+
return f.length === 0 ? [] : [(input?: string) => f.some((i) => input?.endsWith(i))];
|
|
178
|
+
};
|
|
179
|
+
const contains = (n: string | string[]): [ConditionFilter<StringFilter>] | [] => {
|
|
180
|
+
const f = asArray(n);
|
|
181
|
+
return f.length === 0 ? [] : [(input: string) => f.some((i) => input?.includes(i))];
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const conditions = [
|
|
185
|
+
...("startsWith" in config ? startsWith(config.startsWith) : []),
|
|
186
|
+
...("endsWith" in config ? endsWith(config.endsWith) : []),
|
|
187
|
+
...("contains" in config ? contains(config.contains) : []),
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const combined =
|
|
191
|
+
boolLogic === "AND"
|
|
192
|
+
? (input: string) => conditions.every((c) => c(input))
|
|
193
|
+
: (input: string) => conditions.some((c) => c(input));
|
|
194
|
+
|
|
195
|
+
return combined as ConditionFilter<StringFilter>;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const filterFn = <
|
|
199
|
+
F extends FilterDefn | NotFilter,
|
|
200
|
+
C extends LogicalCombinator,
|
|
201
|
+
U extends boolean | "no-impact"
|
|
202
|
+
>(
|
|
203
|
+
defn: F,
|
|
204
|
+
logicCombinator: C,
|
|
205
|
+
ifUndefined: U = "no-impact" as U
|
|
206
|
+
): FilterFn<UnwrapNot<F>> => {
|
|
207
|
+
type D = UnwrapNot<F>;
|
|
208
|
+
const config: D = isNotFilter(defn) ? (defn["not"] as D) : (defn as D);
|
|
209
|
+
|
|
210
|
+
const filter: FilterFn<D> = (
|
|
211
|
+
//
|
|
212
|
+
input: Parameters<FilterFn<D>>[0]
|
|
213
|
+
) => {
|
|
214
|
+
const undefValue =
|
|
215
|
+
ifUndefined === "no-impact"
|
|
216
|
+
? logicCombinator === "AND"
|
|
217
|
+
? true
|
|
218
|
+
: false
|
|
219
|
+
: (ifUndefined as true | false);
|
|
220
|
+
|
|
221
|
+
let flag: boolean;
|
|
222
|
+
|
|
223
|
+
if (typeof input === "undefined") {
|
|
224
|
+
flag = undefValue;
|
|
225
|
+
} else if (isNumericFilter(config)) {
|
|
226
|
+
const fn = numericOps(config, logicCombinator);
|
|
227
|
+
flag = isNotFilter(defn) ? !fn(input as number) : fn(input as number);
|
|
228
|
+
} else {
|
|
229
|
+
const fn = stringOps(config, logicCombinator);
|
|
230
|
+
flag = isNotFilter(defn) ? !fn(input as string) : fn(input as string);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return flag;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return filter;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* **filter**
|
|
241
|
+
*
|
|
242
|
+
* A higher order helper utility which builds a boolean _filter_ function based on a simple
|
|
243
|
+
* configuration object. Support either _string_ or _numeric_ filters.
|
|
244
|
+
*
|
|
245
|
+
* ```ts
|
|
246
|
+
* const str = filter({startsWith: ["_", "."], endsWith: ".md"});
|
|
247
|
+
* const num = filter({ greaterThan: 55, notEqual: [66, 77]});
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* All conditions (e.g., `startsWith`, `contains`, `notEqual`, etc.) that a particular filter
|
|
251
|
+
* is defined as having -- if there is more than one -- will be logically combined using AND
|
|
252
|
+
* unless specified otherwise in the third parameter.
|
|
253
|
+
*
|
|
254
|
+
* How a value of _undefined_ will be treated is stated in the second parameter but defaults
|
|
255
|
+
* to "no-impact" which means it's `false` when the logicCombinator is OR but defaults
|
|
256
|
+
* to `true` when the logicCombinator is AND.
|
|
257
|
+
*/
|
|
258
|
+
export const filter = <
|
|
259
|
+
F extends FilterDefn | NotFilter,
|
|
260
|
+
U extends boolean | "no-impact",
|
|
261
|
+
C extends LogicalCombinator
|
|
262
|
+
>(
|
|
263
|
+
config: F,
|
|
264
|
+
logicCombinator: C = "AND" as C,
|
|
265
|
+
ifUndefined: U = "no-impact" as U
|
|
266
|
+
) => {
|
|
267
|
+
return filterFn(config, logicCombinator, ifUndefined);
|
|
268
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LogicFunction } from "src/types/functions";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Groups a number of "logic functions" together by combining their results using
|
|
5
|
+
* the logical **NOT** operator.
|
|
6
|
+
*
|
|
7
|
+
* **Note:** a "logic function" is any function which returns a boolean
|
|
8
|
+
*/
|
|
9
|
+
export const not = <T extends any[]>(op: LogicFunction<T>): LogicFunction<T> => {
|
|
10
|
+
const fn: LogicFunction<T> = (...args: T) => {
|
|
11
|
+
return !op(...args);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return fn;
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { LogicFunction } from "src/types/functions";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Groups a number of "logic functions" together by combining their results using
|
|
5
|
+
* the logical **OR** operator.
|
|
6
|
+
*
|
|
7
|
+
* **Note:** a "logic function" is any function which returns a boolean
|
|
8
|
+
*/
|
|
9
|
+
export const or = <T extends any[]>(...ops: LogicFunction<T>[]): LogicFunction<T> => {
|
|
10
|
+
const fn: LogicFunction<T> = (...args: T) => {
|
|
11
|
+
return [...ops].some((i) => i(...args));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return fn;
|
|
15
|
+
};
|
package/src/utility/index.ts
CHANGED
|
@@ -10,11 +10,11 @@ export * from "./keys";
|
|
|
10
10
|
export * from "./ruleset";
|
|
11
11
|
// directory exports
|
|
12
12
|
export * from "./api/index";
|
|
13
|
+
export * from "./boolean-logic/index";
|
|
13
14
|
export * from "./dictionary/index";
|
|
14
15
|
export * from "./errors/index";
|
|
15
16
|
export * from "./lists/index";
|
|
16
17
|
export * from "./literals/index";
|
|
17
|
-
export * from "./map-reduce/index";
|
|
18
18
|
export * from "./modelling/index";
|
|
19
19
|
export * from "./runtime/index";
|
|
20
20
|
export * from "./state/index";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Narrowable, Widen } from "src/types";
|
|
2
|
+
import { isArray } from "../runtime";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Type utility which converts `undefined[]` to `unknown[]`
|
|
6
|
+
*/
|
|
7
|
+
export type UndefinedArrayIsUnknown<T extends any[]> = undefined[] extends T ? unknown[] : T;
|
|
8
|
+
|
|
9
|
+
export type AsArray<T, W extends boolean = false> = T extends any[]
|
|
10
|
+
? W extends true
|
|
11
|
+
? Widen<T>
|
|
12
|
+
: T
|
|
13
|
+
: W extends true
|
|
14
|
+
? UndefinedArrayIsUnknown<Widen<T>[]>
|
|
15
|
+
: UndefinedArrayIsUnknown<T[]>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Ensures that any input passed in is passed back as an array:
|
|
19
|
+
*
|
|
20
|
+
* - if it was already an array than this just serves as an _identity_ function
|
|
21
|
+
* - if it was not then it wraps the element into a one element array of the
|
|
22
|
+
* given type
|
|
23
|
+
*
|
|
24
|
+
* Note: by default the _type_ of values will be intentionally widened so that the value "abc"
|
|
25
|
+
* is of type `string` not the literal `abc`. If you want to keep literal types then
|
|
26
|
+
* change the optional _widen_ parameter to _false_.
|
|
27
|
+
*/
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
export const asArray = <T extends Narrowable, W extends boolean = true>(thing: T, _widen?: W) => {
|
|
30
|
+
return (isArray(thing) ? thing : typeof thing === "undefined" ? ([] as T[]) : [thing]) as AsArray<
|
|
31
|
+
T,
|
|
32
|
+
W
|
|
33
|
+
>;
|
|
34
|
+
};
|
package/tests/Includes-spec.ts
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
|
|
3
3
|
import type { Includes } from "../src/types";
|
|
4
|
-
import type {
|
|
4
|
+
import type { Equal, Expect } from "@type-challenges/utils";
|
|
5
5
|
|
|
6
6
|
describe("Includes type check", () => {
|
|
7
|
-
it("Includes
|
|
7
|
+
it("Includes works on a string source", () => {
|
|
8
|
+
type T = Includes<"Hello World", "Hello">;
|
|
9
|
+
type F = Includes<"Hello World", "nada">;
|
|
10
|
+
type U = Includes<string, "who cares">;
|
|
11
|
+
type N = Includes<"Hello World", string>;
|
|
8
12
|
type cases = [
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
Expect<Equal<T, true>>, //
|
|
14
|
+
Expect<Equal<F, false>>,
|
|
15
|
+
Expect<Equal<U, boolean>>,
|
|
16
|
+
Expect<Equal<N, boolean>>
|
|
11
17
|
];
|
|
12
|
-
const typeTests: cases = [true,
|
|
18
|
+
const typeTests: cases = [true, true, true, true];
|
|
19
|
+
expect(typeTests).toBe(typeTests);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("Includes works on a string[] source", () => {
|
|
23
|
+
type T = Includes<["Hello", "World"], "Hello">;
|
|
24
|
+
type F = Includes<["Hello", "World"], "nada">;
|
|
25
|
+
type U = Includes<string[], "who cares">;
|
|
26
|
+
type N = Includes<["Hello", "World"], string>;
|
|
27
|
+
type cases = [
|
|
28
|
+
Expect<Equal<T, true>>, //
|
|
29
|
+
Expect<Equal<F, false>>,
|
|
30
|
+
Expect<Equal<U, boolean>>,
|
|
31
|
+
Expect<Equal<N, boolean>>
|
|
32
|
+
];
|
|
33
|
+
const typeTests: cases = [true, true, true, true];
|
|
13
34
|
expect(typeTests).toBe(typeTests);
|
|
14
35
|
});
|
|
15
36
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Equal, Expect } from "@type-challenges/utils";
|
|
3
|
+
import { IsBooleanLiteral, IsLiteral } from "src/types";
|
|
4
|
+
|
|
5
|
+
describe("IsLiteral<T> type utility", () => {
|
|
6
|
+
it("string values", () => {
|
|
7
|
+
const s = "hi" as string;
|
|
8
|
+
const sl = "hi" as const;
|
|
9
|
+
|
|
10
|
+
type cases = [
|
|
11
|
+
Expect<Equal<IsLiteral<typeof s>, false>>,
|
|
12
|
+
Expect<Equal<IsLiteral<typeof sl>, true>>
|
|
13
|
+
];
|
|
14
|
+
const cases: cases = [true, true];
|
|
15
|
+
expect(typeof s).toBe("string");
|
|
16
|
+
expect(typeof sl).toBe("string");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("numeric values", () => {
|
|
20
|
+
const v = 42 as number;
|
|
21
|
+
const vl = 42 as const;
|
|
22
|
+
|
|
23
|
+
type cases = [
|
|
24
|
+
Expect<Equal<IsLiteral<typeof v>, false>>,
|
|
25
|
+
Expect<Equal<IsLiteral<typeof vl>, true>>
|
|
26
|
+
];
|
|
27
|
+
const cases: cases = [true, true];
|
|
28
|
+
|
|
29
|
+
expect(typeof v).toBe("number");
|
|
30
|
+
expect(typeof vl).toBe("number");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("boolean values", () => {
|
|
34
|
+
const v = true as boolean;
|
|
35
|
+
const vl = false as const;
|
|
36
|
+
|
|
37
|
+
type cases = [
|
|
38
|
+
// wide
|
|
39
|
+
Expect<Equal<IsBooleanLiteral<typeof v>, false>>,
|
|
40
|
+
Expect<Equal<IsLiteral<typeof v>, false>>,
|
|
41
|
+
// literal
|
|
42
|
+
Expect<Equal<IsBooleanLiteral<typeof vl>, true>>,
|
|
43
|
+
Expect<Equal<IsLiteral<typeof vl>, true>>
|
|
44
|
+
];
|
|
45
|
+
const cases: cases = [true, true, true, true];
|
|
46
|
+
expect(typeof v).toBe("boolean");
|
|
47
|
+
expect(typeof vl).toBe("boolean");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
// import type { Equal, Expect } from "@type-challenges/utils";
|
|
3
|
+
import { and, filter } from "src/utility";
|
|
4
|
+
|
|
5
|
+
describe("boolean logic", () => {
|
|
6
|
+
it("AND operation", () => {
|
|
7
|
+
const a1 = and(
|
|
8
|
+
() => true,
|
|
9
|
+
() => false
|
|
10
|
+
);
|
|
11
|
+
const a2 = and(
|
|
12
|
+
() => true,
|
|
13
|
+
() => true
|
|
14
|
+
);
|
|
15
|
+
const md = and(filter({ endsWith: ".md" }), filter({ not: { startsWith: "_" } }));
|
|
16
|
+
|
|
17
|
+
expect(a1()).toBe(false);
|
|
18
|
+
expect(a2()).toBe(true);
|
|
19
|
+
expect(["foo.md", "bar.html", "_baz.md"].filter(md)).toEqual(["foo.md"]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { filter } from "src/utility";
|
|
2
|
+
import { Equal, Expect } from "@type-challenges/utils";
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
|
|
5
|
+
describe("filter() utility function", () => {
|
|
6
|
+
it("string filter built and gives proper types", () => {
|
|
7
|
+
const f = filter({ startsWith: "th" });
|
|
8
|
+
type P = Parameters<typeof f>;
|
|
9
|
+
type R = ReturnType<typeof f>;
|
|
10
|
+
const notPrivate = filter({ not: { startsWith: ["_", "."] } });
|
|
11
|
+
|
|
12
|
+
// runtime
|
|
13
|
+
expect(typeof f).toEqual("function");
|
|
14
|
+
expect(["one", "two", "three"].filter(f)).toEqual(["three"]);
|
|
15
|
+
expect(["foo.md", "bar.html", "_private.md", ".bobs-your-uncle.txt"].filter(notPrivate)) //
|
|
16
|
+
.toEqual(["foo.md", "bar.html"]);
|
|
17
|
+
|
|
18
|
+
// design time
|
|
19
|
+
type cases = [
|
|
20
|
+
Expect<Equal<P[0], string | undefined>>, //
|
|
21
|
+
Expect<Equal<R, boolean>>
|
|
22
|
+
];
|
|
23
|
+
const cases: cases = [true, true];
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("numeric filter built and gives proper types", () => {
|
|
27
|
+
const f = filter({ equals: 42 });
|
|
28
|
+
type P = Parameters<typeof f>;
|
|
29
|
+
type R = ReturnType<typeof f>;
|
|
30
|
+
|
|
31
|
+
// runtime
|
|
32
|
+
expect(typeof f).toEqual("function");
|
|
33
|
+
expect([1, 2, 3, 4, 5].filter(filter({ greaterThan: 3 }))) //
|
|
34
|
+
.toEqual([4, 5]);
|
|
35
|
+
expect([1, 2, 3, 4, 5].filter(filter({ not: { greaterThan: 3 } }))) //
|
|
36
|
+
.toEqual([1, 2, 3]);
|
|
37
|
+
|
|
38
|
+
// design time
|
|
39
|
+
type cases = [
|
|
40
|
+
Expect<Equal<P[0], number | undefined>>, //
|
|
41
|
+
Expect<Equal<R, boolean>>
|
|
42
|
+
];
|
|
43
|
+
const cases: cases = [true, true];
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("string filter's startsWith", () => {
|
|
47
|
+
const f = filter({ startsWith: "." });
|
|
48
|
+
const remaining = ["foo", "bar", ".baz"].filter(f);
|
|
49
|
+
|
|
50
|
+
expect(remaining).toEqual([".baz"]);
|
|
51
|
+
});
|
|
52
|
+
});
|