assertie 0.2.0 → 0.3.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/CHANGELOG.md +23 -0
- package/MIGRATION-GUIDE.md +12 -0
- package/README.md +31 -1
- package/lib/index.d.ts +147 -22
- package/lib/index.js +258 -52
- package/package.json +4 -4
- package/src/index.ts +384 -62
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.1](https://github.com/OfficialHalfwayDead/assertie/compare/v0.3.0...v0.3.1) (2025-02-23)
|
|
4
|
+
|
|
5
|
+
* [`0d59837`](https://github.com/OfficialHalfwayDead/assertie/commit/0d59837841707c963ce174e79a527fcb1379ac4f) Add array and tuple assertions to the README.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## [0.3.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.2.0...v0.3.0) (2025-02-23)
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
* [`9906290`](https://github.com/OfficialHalfwayDead/assertie/commit/990629053a10e519c4d73ece8825f8edf7003489) assertie >= 0.3.0 requires TypeScript 4.7.0 or higher in your project.
|
|
13
|
+
* [`b8ca296`](https://github.com/OfficialHalfwayDead/assertie/commit/b8ca296cb80c8e46db49af8256ea8bc1f76532e4) `assertPropsNonNullable` now only accepts objects as the first argument. This is technically a breaking change because TypeScript would have previously allowed passing anything to it, but it never made sense.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* [`7188088`](https://github.com/OfficialHalfwayDead/assertie/commit/71880883ecc0ecc110a6074b955dc2fc5fe806a5) More accurate print of the actual type of items when an assertion fails and significantly improved error messages overall.
|
|
18
|
+
* [`7188088`](https://github.com/OfficialHalfwayDead/assertie/commit/71880883ecc0ecc110a6074b955dc2fc5fe806a5) Add type and non-null assertions for arrays that check all elements in one call and provide better typing and error messages: `assertArrayType`, `assertArrayNonNullable`.
|
|
19
|
+
* [`9906290`](https://github.com/OfficialHalfwayDead/assertie/commit/990629053a10e519c4d73ece8825f8edf7003489) Add assertions for tuples that can narrow types of arrays or tuples with unknown types: `assertIsTuple`, `assertTupleTypes`, `assertTupleNonNullable`.
|
|
20
|
+
|
|
21
|
+
### Other
|
|
22
|
+
|
|
23
|
+
* [`075fecd`](https://github.com/OfficialHalfwayDead/assertie/commit/075fecd105030191eac671d8731765f4f5af2cd4) Add JSDoc comments to all functions and improve documentation overall.
|
|
24
|
+
|
|
25
|
+
|
|
3
26
|
## [0.2.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.1.0...v0.2.0) (2025-02-17)
|
|
4
27
|
|
|
5
28
|
### Breaking Changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
Scroll down to your previous version and then follow all the steps upwards in order until the version you're targeting.
|
|
4
|
+
|
|
5
|
+
## [0.2.x to 0.3.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.2.0...v0.3.0)
|
|
6
|
+
|
|
7
|
+
* New features mean assertie >= 0.3.0 requires TypeScript 4.7.0 or higher in your project. (Previous minimum version was 3.7.0)
|
|
8
|
+
* Ensure that you aren't passing things that aren't objects to `assertPropsNonNullable`. It never made sense, but the TS type previously allowed it.
|
|
9
|
+
|
|
10
|
+
## [0.1.x to 0.2.0](https://github.com/OfficialHalfwayDead/assertie/compare/v0.1.0...v0.2.0)
|
|
11
|
+
|
|
12
|
+
* All functions which had `Typeof` in the name changed capitalization to `TypeOf` for consistency. Change all existing calls to the new spelling.
|
package/README.md
CHANGED
|
@@ -154,6 +154,28 @@ const safeObj = obj;
|
|
|
154
154
|
|
|
155
155
|
The reason an array is needed here is because undefined properties may not be present in `Object.keys`, so the caller needs to provide all keys to check. Don't worry about the safety, though. If you forget to pass a key, its type will remain nullable after the assert and TypeScript will not consider it safe to access.
|
|
156
156
|
|
|
157
|
+
### Arrays and tuples
|
|
158
|
+
|
|
159
|
+
Arrays and tuples have equivalent versions of `assertType` and `assertNonNullable`. These make it easy to check every element at once and get great errors and type narrowing.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const arr: (number | string | null)[] = [1, 2, 3];
|
|
163
|
+
assertArrayNonNullable(arr); // narrows to (number | string)[]
|
|
164
|
+
assertArrayType(arr, "number"); // narrows to number[]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const arr: number[] = [1, 2, 3];
|
|
169
|
+
assertIsTuple(arr, 3); // narrows to [number, number, number]
|
|
170
|
+
|
|
171
|
+
const arrMixed: (number | string | null)[] = [1, "a"];
|
|
172
|
+
assertIsTuple(arrMixed, 2); // narrows to [T, T]
|
|
173
|
+
// where T = number | string | null;
|
|
174
|
+
assertTupleNonNullable(arrMixed); // narrows T to number | string
|
|
175
|
+
assertTupleTypes(arrMixed, ["number", "string"]);
|
|
176
|
+
const tup: [number, string] = arrMixed;
|
|
177
|
+
```
|
|
178
|
+
|
|
157
179
|
### Asserting unreachable code
|
|
158
180
|
|
|
159
181
|
The unreachable assertion will
|
|
@@ -186,4 +208,12 @@ assertFiniteNumber(123);
|
|
|
186
208
|
// DO NOT USE FOR INPUT VALIDATION OF USER PROVIDED VALUES!
|
|
187
209
|
```
|
|
188
210
|
|
|
189
|
-
|
|
211
|
+
Can prevent `NaN` propagation and accidental infinities in calculations. Also useful for string-to-number conversions when you expect **valid** number strings:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const str = "CustomObject123";
|
|
215
|
+
const numStr = str.substring(11); // oops "t123"
|
|
216
|
+
const num = Number(numStr); // NaN
|
|
217
|
+
assertFiniteNumber(num); // throws
|
|
218
|
+
arr[num] = "yup" // disaster averted
|
|
219
|
+
```
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
type
|
|
2
|
-
type PrimitiveTypes = {
|
|
1
|
+
declare type Tuple<T, N extends number, A extends unknown[] = []> = A["length"] extends N ? A : Tuple<T, N, [...A, T]>;
|
|
2
|
+
declare type PrimitiveTypes = {
|
|
3
3
|
"string": string;
|
|
4
4
|
"number": number;
|
|
5
5
|
"boolean": boolean;
|
|
@@ -9,29 +9,154 @@ type PrimitiveTypes = {
|
|
|
9
9
|
"object": object;
|
|
10
10
|
"symbol": symbol;
|
|
11
11
|
};
|
|
12
|
-
type
|
|
12
|
+
declare type PrimitiveTypeStrings = keyof PrimitiveTypes;
|
|
13
|
+
declare type NullableKeys<T> = {
|
|
13
14
|
[K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never;
|
|
14
15
|
}[keyof T];
|
|
15
|
-
type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
|
|
16
|
+
declare type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
|
|
16
17
|
[K in N]-?: NonNullable<T[K]>;
|
|
17
18
|
};
|
|
18
|
-
type Constructor<T> = new (...args: unknown[]) => T;
|
|
19
|
-
type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
|
|
20
|
-
type ResolveAnyJSType<T extends AllJSTypes> = T extends PrimitiveTypeStrings ? PrimitiveTypes[T] : T extends null ? null : T extends undefined ? undefined : T extends Constructor<infer U> ? U : never;
|
|
19
|
+
declare type Constructor<T> = new (...args: unknown[]) => T;
|
|
20
|
+
declare type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
|
|
21
|
+
declare type ResolveAnyJSType<T extends AllJSTypes> = T extends PrimitiveTypeStrings ? PrimitiveTypes[T] : T extends null ? null : T extends undefined ? undefined : T extends Constructor<infer U> ? U : never;
|
|
22
|
+
/**
|
|
23
|
+
* Asserts that the provided boolean is true.
|
|
24
|
+
* @param {boolean} hasToBeTrue - The boolean to assert.
|
|
25
|
+
* @param {string} msg - The message of the Error if the assertion fails.
|
|
26
|
+
* @throws {AssertionError} if the assertion fails.
|
|
27
|
+
*/
|
|
21
28
|
export declare function assert(hasToBeTrue: boolean, msg?: string): asserts hasToBeTrue is true;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export declare function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export declare function
|
|
36
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Asserts that the provided object is of the expectedType.
|
|
31
|
+
* @param {unknown} item - The object which ought to be of the expectedType.
|
|
32
|
+
* @param {AllJSTypes} expectedType - The expected type of the object. JS primitive types, null, undefined, and constructable types are supported. JS primitive types are passed as the string they return from typeof, e.g., "number".
|
|
33
|
+
* @throws {AssertionError} if the type isn't as expected.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertType<T extends AllJSTypes>(item: unknown, expectedType: T): asserts item is ResolveAnyJSType<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse (even when the expectedType is undefined).
|
|
38
|
+
* @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" => arr: number[]
|
|
39
|
+
* @param {AllJSTypes} expectedType - The expected type of individual items. JS primitive types, null, undefined, and constructable types are supported.
|
|
40
|
+
* @throws {AssertionError} if the type isn't as expected.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertArrayType<T extends AllJSTypes>(arr: unknown[], expectedType: T): asserts arr is ResolveAnyJSType<T>[];
|
|
43
|
+
/**
|
|
44
|
+
* Asserts that the array or tuple has the expected types at each index.
|
|
45
|
+
* @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the length and types.
|
|
46
|
+
* @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., expectedTypes = ["number", "string", Date] => arrayOrTuple: [number, string, Date]. The individual entries can be JS primitive types, null, undefined, and constructors.
|
|
47
|
+
* @throws {AssertionError} if the type of any element of the tuple isn't as expected.
|
|
48
|
+
*/
|
|
49
|
+
export declare function assertTupleTypes<T extends readonly AllJSTypes[], U extends {
|
|
50
|
+
[K in keyof T]: unknown;
|
|
51
|
+
} | (number extends U["length"] ? unknown[] : never)>(arrayOrTuple: U, expectedTypes: readonly [...T]): asserts arrayOrTuple is U & {
|
|
52
|
+
[K in keyof T]: ResolveAnyJSType<T[K]>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Asserts that the provided item is of type string.
|
|
56
|
+
* @param {unknown} item - The item which ought to be of type string.
|
|
57
|
+
* @throws {AssertionError} if the type isn't string.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertTypeOfString(item: unknown): asserts item is string;
|
|
60
|
+
/**
|
|
61
|
+
* Asserts that the provided item is of type number.
|
|
62
|
+
* @param {unknown} item - The item which ought to be of type number.
|
|
63
|
+
* @throws {AssertionError} if the type isn't number.
|
|
64
|
+
*/
|
|
65
|
+
export declare function assertTypeOfNumber(item: unknown): asserts item is number;
|
|
66
|
+
/**
|
|
67
|
+
* Asserts that the provided item is of type boolean.
|
|
68
|
+
* @param {unknown} item - The item which ought to be of type boolean.
|
|
69
|
+
* @throws {AssertionError} if the type isn't boolean.
|
|
70
|
+
*/
|
|
71
|
+
export declare function assertTypeOfBoolean(item: unknown): asserts item is boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Asserts that the provided item is of type bigint.
|
|
74
|
+
* @param {unknown} item - The item which ought to be of type bigint.
|
|
75
|
+
* @throws {AssertionError} if the type isn't bigint.
|
|
76
|
+
*/
|
|
77
|
+
export declare function assertTypeOfBigint(item: unknown): asserts item is bigint;
|
|
78
|
+
/**
|
|
79
|
+
* Asserts that the provided item is of type undefined.
|
|
80
|
+
* @param {unknown} item - The item which ought to be of type undefined.
|
|
81
|
+
* @throws {AssertionError} if the type isn't undefined.
|
|
82
|
+
*/
|
|
83
|
+
export declare function assertTypeOfUndefined(item: unknown): asserts item is undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Asserts that the provided item is of type function.
|
|
86
|
+
* @param {unknown} item - The item which ought to be of type function.
|
|
87
|
+
* @throws {AssertionError} if the type isn't function.
|
|
88
|
+
*/
|
|
89
|
+
export declare function assertTypeOfFunction(item: unknown): asserts item is Function;
|
|
90
|
+
/**
|
|
91
|
+
* Asserts that the provided item is of type object.
|
|
92
|
+
* @param {unknown} item - The item which ought to be of type object.
|
|
93
|
+
* @throws {AssertionError} if the type isn't object.
|
|
94
|
+
*/
|
|
95
|
+
export declare function assertTypeOfObject(item: unknown): asserts item is object;
|
|
96
|
+
/**
|
|
97
|
+
* Asserts that the provided item is of type symbol.
|
|
98
|
+
* @param {unknown} item - The item which ought to be of type symbol.
|
|
99
|
+
* @throws {AssertionError} if the type isn't symbol.
|
|
100
|
+
*/
|
|
101
|
+
export declare function assertTypeOfSymbol(item: unknown): asserts item is symbol;
|
|
102
|
+
/**
|
|
103
|
+
* Asserts that the provided item is null.
|
|
104
|
+
* @param {unknown} item - The item which ought to be null.
|
|
105
|
+
* @throws {AssertionError} if the value isn't null.
|
|
106
|
+
*/
|
|
107
|
+
export declare function assertNull(item: unknown): asserts item is null;
|
|
108
|
+
/**
|
|
109
|
+
* Asserts that the provided item is an instance of the provided constructor.
|
|
110
|
+
* @param {unknown} item - The item which ought to be an instance of the constructor.
|
|
111
|
+
* @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
|
|
112
|
+
* @throws {AssertionError} if item instanceof constructor is false.
|
|
113
|
+
*/
|
|
114
|
+
export declare function assertInstanceOf<T>(item: unknown, constructor: Constructor<T>): asserts item is T;
|
|
115
|
+
/**
|
|
116
|
+
* Asserts that the provided array is a tuple of exactly the expected length.
|
|
117
|
+
* @param {unknown[]} arr - The array which ought to be a tuple.
|
|
118
|
+
* @param {number} expectedLength - The exact expected length of the tuple.
|
|
119
|
+
* @throws {AssertionError} if the array isn't of the expected length or is sparse.
|
|
120
|
+
*/
|
|
121
|
+
export declare function assertIsTuple<T extends number extends T["length"] ? unknown[] : never, N extends number>(arr: [...T], expectedLength: N): asserts arr is T & Tuple<T[number], N>;
|
|
122
|
+
/**
|
|
123
|
+
* Used to assert that code can never be reached. Pass a value which has already been checked for all types that should be possible. If the range of possible values increases, TypeScript will throw an error at compile time because the value won't be of type never.
|
|
124
|
+
* @param {never} item - An exhausted value, of which all cases are accounted for in other branches of the code, such as at the end of a switch statement.
|
|
125
|
+
* @param {string} msg - Override the default error message. Even if you do, the error message will include the value and type of item.
|
|
126
|
+
* @throws {AssertionError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
|
|
127
|
+
*/
|
|
128
|
+
export declare function assertUnreachable(item: never, msg?: string): asserts item is never;
|
|
129
|
+
/**
|
|
130
|
+
* Asserts that the provided item is neither null nor undefined.
|
|
131
|
+
* @param {unknown} item - The item which ought to be non-null.
|
|
132
|
+
* @throws {AssertionError} if the item is null or undefined.
|
|
133
|
+
*/
|
|
134
|
+
export declare function assertNonNullable<T>(item: T): asserts item is NonNullable<T>;
|
|
135
|
+
/**
|
|
136
|
+
* Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
|
|
137
|
+
* @param {object} obj - The object which ought to have the properties.
|
|
138
|
+
* @param {NullableKeys<T>} propKeys - An array of the stringified keys of the properties which ought to be non-null in the object.
|
|
139
|
+
* @throws {AssertionError} if any of the properties was null, undefined, or not present in the object.
|
|
140
|
+
*/
|
|
141
|
+
export declare function assertPropsNonNullable<T extends object, N extends NullableKeys<T>>(obj: T, propKeys: N[]): asserts obj is PropsNonNullable<T, N>;
|
|
142
|
+
/**
|
|
143
|
+
* Asserts that all elements of the provided array are neither null nor undefined, or not present.
|
|
144
|
+
* @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements.
|
|
145
|
+
* @throws {AssertionError} if any of the elements was null, undefined, or not present in the array.
|
|
146
|
+
*/
|
|
147
|
+
export declare function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<T>[];
|
|
148
|
+
/**
|
|
149
|
+
* Asserts that the provided tuple has non-null values for all elements. This function does not take a length. So if you want to assert that the typescript tuple type is of the correct length, call @see assertIsTuple first.
|
|
150
|
+
* @param {[unknown, ...]} tuple - The tuple which ought to have only non-null values.
|
|
151
|
+
* @throws {AssertionError} if any of the elements was null, undefined, or an index not present in the tuple.
|
|
152
|
+
*/
|
|
153
|
+
export declare function assertTupleNonNullable<T extends number extends T["length"] ? never : unknown[]>(tuple: T): asserts tuple is {
|
|
154
|
+
[K in keyof T]: NonNullable<T[K]>;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Asserts that the provided item is a finite number. Use to prevent NaN propagation.
|
|
158
|
+
* @param {unknown} item - The item which ought to be a finite number.
|
|
159
|
+
* @throws {AssertionError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
|
|
160
|
+
*/
|
|
161
|
+
export declare function assertFiniteNumber(item: unknown): asserts item is number;
|
|
37
162
|
export {};
|
package/lib/index.js
CHANGED
|
@@ -1,113 +1,319 @@
|
|
|
1
|
+
function getNameOfExpectedType(expectedType) {
|
|
2
|
+
if (expectedType === null)
|
|
3
|
+
return "null";
|
|
4
|
+
if (expectedType === undefined)
|
|
5
|
+
return "undefined";
|
|
6
|
+
if (typeof expectedType === "string")
|
|
7
|
+
return expectedType;
|
|
8
|
+
return expectedType.name;
|
|
9
|
+
}
|
|
10
|
+
function getTypeNameOfUnknown(item) {
|
|
11
|
+
if (item === null)
|
|
12
|
+
return "null";
|
|
13
|
+
if (item === undefined)
|
|
14
|
+
return "undefined";
|
|
15
|
+
try {
|
|
16
|
+
if (item instanceof item.constructor && item.constructor.name !== "Function") {
|
|
17
|
+
// I'd like function to match the primitive name "function"
|
|
18
|
+
// because that's how the asserts are written.
|
|
19
|
+
return item.constructor.name;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
return typeof item;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isType(item, expectedType) {
|
|
27
|
+
if (typeof item === expectedType)
|
|
28
|
+
return true;
|
|
29
|
+
const reducedExpectedType = expectedType;
|
|
30
|
+
if (item === reducedExpectedType)
|
|
31
|
+
return true;
|
|
32
|
+
const remainingOption = expectedType;
|
|
33
|
+
if (item instanceof remainingOption)
|
|
34
|
+
return true;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
1
37
|
class AssertionError extends Error {
|
|
2
38
|
constructor(msg) {
|
|
3
39
|
super(`Assertion failed: ${msg}`);
|
|
4
40
|
this.name = AssertionError.name;
|
|
5
41
|
}
|
|
6
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Asserts that the provided boolean is true.
|
|
45
|
+
* @param {boolean} hasToBeTrue - The boolean to assert.
|
|
46
|
+
* @param {string} msg - The message of the Error if the assertion fails.
|
|
47
|
+
* @throws {AssertionError} if the assertion fails.
|
|
48
|
+
*/
|
|
7
49
|
export function assert(hasToBeTrue, msg = "No specific message provided.") {
|
|
8
50
|
if (!import.meta.env.DEV)
|
|
9
51
|
return;
|
|
10
52
|
if (!hasToBeTrue)
|
|
11
53
|
throw new AssertionError(msg);
|
|
12
54
|
}
|
|
13
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Asserts that the provided object is of the expectedType.
|
|
57
|
+
* @param {unknown} item - The object which ought to be of the expectedType.
|
|
58
|
+
* @param {AllJSTypes} expectedType - The expected type of the object. JS primitive types, null, undefined, and constructable types are supported. JS primitive types are passed as the string they return from typeof, e.g., "number".
|
|
59
|
+
* @throws {AssertionError} if the type isn't as expected.
|
|
60
|
+
*/
|
|
61
|
+
export function assertType(item, expectedType) {
|
|
14
62
|
if (!import.meta.env.DEV)
|
|
15
63
|
return;
|
|
16
|
-
if (
|
|
64
|
+
if (!isType(item, expectedType))
|
|
65
|
+
throw new AssertionError(`Provided object was not of type ${getNameOfExpectedType(expectedType)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse (even when the expectedType is undefined).
|
|
69
|
+
* @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" => arr: number[]
|
|
70
|
+
* @param {AllJSTypes} expectedType - The expected type of individual items. JS primitive types, null, undefined, and constructable types are supported.
|
|
71
|
+
* @throws {AssertionError} if the type isn't as expected.
|
|
72
|
+
*/
|
|
73
|
+
export function assertArrayType(arr, expectedType) {
|
|
74
|
+
if (!import.meta.env.DEV)
|
|
17
75
|
return;
|
|
18
|
-
|
|
19
|
-
|
|
76
|
+
for (let i = 0; i < arr.length; i++) {
|
|
77
|
+
if (!(i in arr))
|
|
78
|
+
throw new AssertionError(`Array to assert type of was sparse with a missing item at index ${i}`);
|
|
79
|
+
const item = arr[i];
|
|
80
|
+
if (!isType(item, expectedType))
|
|
81
|
+
throw new AssertionError(`Provided array had item at index ${i} not of type ${getNameOfExpectedType(expectedType)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Asserts that the array or tuple has the expected types at each index.
|
|
86
|
+
* @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the length and types.
|
|
87
|
+
* @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., expectedTypes = ["number", "string", Date] => arrayOrTuple: [number, string, Date]. The individual entries can be JS primitive types, null, undefined, and constructors.
|
|
88
|
+
* @throws {AssertionError} if the type of any element of the tuple isn't as expected.
|
|
89
|
+
*/
|
|
90
|
+
export function assertTupleTypes(arrayOrTuple, expectedTypes) {
|
|
91
|
+
if (!import.meta.env.DEV)
|
|
20
92
|
return;
|
|
21
|
-
|
|
22
|
-
|
|
93
|
+
if (arrayOrTuple.length !== expectedTypes.length) {
|
|
94
|
+
throw new AssertionError(`Provided tuple length mismatch: expected ${expectedTypes.length}, but got ${arrayOrTuple.length}`);
|
|
95
|
+
}
|
|
96
|
+
for (let i = 0; i < expectedTypes.length; i++) {
|
|
97
|
+
if (!(i in arrayOrTuple))
|
|
98
|
+
throw new AssertionError(`Provided tuple was sparse with a missing item at required index ${i}`);
|
|
99
|
+
const item = arrayOrTuple[i];
|
|
100
|
+
if (!isType(item, expectedTypes[i])) {
|
|
101
|
+
throw new AssertionError(`Provided tuple had item at index ${i} not of type ${getNameOfExpectedType(expectedTypes[i])}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Asserts that the provided item is of type string.
|
|
107
|
+
* @param {unknown} item - The item which ought to be of type string.
|
|
108
|
+
* @throws {AssertionError} if the type isn't string.
|
|
109
|
+
*/
|
|
110
|
+
export function assertTypeOfString(item) {
|
|
111
|
+
if (!import.meta.env.DEV)
|
|
112
|
+
return;
|
|
113
|
+
if (typeof item !== "string")
|
|
114
|
+
throw new AssertionError(`Provided item was not of type string. Was: ${getTypeNameOfUnknown(item)}`);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Asserts that the provided item is of type number.
|
|
118
|
+
* @param {unknown} item - The item which ought to be of type number.
|
|
119
|
+
* @throws {AssertionError} if the type isn't number.
|
|
120
|
+
*/
|
|
121
|
+
export function assertTypeOfNumber(item) {
|
|
122
|
+
if (!import.meta.env.DEV)
|
|
23
123
|
return;
|
|
24
|
-
|
|
124
|
+
if (typeof item !== "number")
|
|
125
|
+
throw new AssertionError(`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`);
|
|
25
126
|
}
|
|
26
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Asserts that the provided item is of type boolean.
|
|
129
|
+
* @param {unknown} item - The item which ought to be of type boolean.
|
|
130
|
+
* @throws {AssertionError} if the type isn't boolean.
|
|
131
|
+
*/
|
|
132
|
+
export function assertTypeOfBoolean(item) {
|
|
27
133
|
if (!import.meta.env.DEV)
|
|
28
134
|
return;
|
|
29
|
-
if (typeof
|
|
30
|
-
throw new AssertionError(`Provided
|
|
135
|
+
if (typeof item !== "boolean")
|
|
136
|
+
throw new AssertionError(`Provided item was not of type boolean. Was: ${getTypeNameOfUnknown(item)}`);
|
|
31
137
|
}
|
|
32
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Asserts that the provided item is of type bigint.
|
|
140
|
+
* @param {unknown} item - The item which ought to be of type bigint.
|
|
141
|
+
* @throws {AssertionError} if the type isn't bigint.
|
|
142
|
+
*/
|
|
143
|
+
export function assertTypeOfBigint(item) {
|
|
33
144
|
if (!import.meta.env.DEV)
|
|
34
145
|
return;
|
|
35
|
-
if (typeof
|
|
36
|
-
throw new AssertionError(`Provided
|
|
146
|
+
if (typeof item !== "bigint")
|
|
147
|
+
throw new AssertionError(`Provided item was not of type bigint. Was: ${getTypeNameOfUnknown(item)}`);
|
|
37
148
|
}
|
|
38
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Asserts that the provided item is of type undefined.
|
|
151
|
+
* @param {unknown} item - The item which ought to be of type undefined.
|
|
152
|
+
* @throws {AssertionError} if the type isn't undefined.
|
|
153
|
+
*/
|
|
154
|
+
export function assertTypeOfUndefined(item) {
|
|
39
155
|
if (!import.meta.env.DEV)
|
|
40
156
|
return;
|
|
41
|
-
if (typeof
|
|
42
|
-
throw new AssertionError(`Provided
|
|
157
|
+
if (typeof item !== "undefined")
|
|
158
|
+
throw new AssertionError(`Provided item was not of type undefined. Was: ${getTypeNameOfUnknown(item)}`);
|
|
43
159
|
}
|
|
44
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Asserts that the provided item is of type function.
|
|
162
|
+
* @param {unknown} item - The item which ought to be of type function.
|
|
163
|
+
* @throws {AssertionError} if the type isn't function.
|
|
164
|
+
*/
|
|
165
|
+
export function assertTypeOfFunction(item) {
|
|
45
166
|
if (!import.meta.env.DEV)
|
|
46
167
|
return;
|
|
47
|
-
if (typeof
|
|
48
|
-
throw new AssertionError(`Provided
|
|
168
|
+
if (typeof item !== "function")
|
|
169
|
+
throw new AssertionError(`Provided item was not of type function. Was: ${getTypeNameOfUnknown(item)}`);
|
|
49
170
|
}
|
|
50
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Asserts that the provided item is of type object.
|
|
173
|
+
* @param {unknown} item - The item which ought to be of type object.
|
|
174
|
+
* @throws {AssertionError} if the type isn't object.
|
|
175
|
+
*/
|
|
176
|
+
export function assertTypeOfObject(item) {
|
|
51
177
|
if (!import.meta.env.DEV)
|
|
52
178
|
return;
|
|
53
|
-
if (typeof
|
|
54
|
-
throw new AssertionError(`Provided
|
|
179
|
+
if (typeof item !== "object")
|
|
180
|
+
throw new AssertionError(`Provided item was not of type object. Was: ${getTypeNameOfUnknown(item)}`);
|
|
55
181
|
}
|
|
56
|
-
|
|
57
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Asserts that the provided item is of type symbol.
|
|
184
|
+
* @param {unknown} item - The item which ought to be of type symbol.
|
|
185
|
+
* @throws {AssertionError} if the type isn't symbol.
|
|
186
|
+
*/
|
|
187
|
+
export function assertTypeOfSymbol(item) {
|
|
58
188
|
if (!import.meta.env.DEV)
|
|
59
189
|
return;
|
|
60
|
-
if (typeof
|
|
61
|
-
throw new AssertionError(`Provided
|
|
190
|
+
if (typeof item !== "symbol")
|
|
191
|
+
throw new AssertionError(`Provided item was not of type symbol. Was: ${getTypeNameOfUnknown(item)}`);
|
|
62
192
|
}
|
|
63
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Asserts that the provided item is null.
|
|
195
|
+
* @param {unknown} item - The item which ought to be null.
|
|
196
|
+
* @throws {AssertionError} if the value isn't null.
|
|
197
|
+
*/
|
|
198
|
+
export function assertNull(item) {
|
|
64
199
|
if (!import.meta.env.DEV)
|
|
65
200
|
return;
|
|
66
|
-
if (
|
|
67
|
-
throw new AssertionError(`Provided
|
|
201
|
+
if (item !== null)
|
|
202
|
+
throw new AssertionError(`Provided item was not null. Was type: ${getTypeNameOfUnknown(item)}, value: ${item}`);
|
|
68
203
|
}
|
|
69
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Asserts that the provided item is an instance of the provided constructor.
|
|
206
|
+
* @param {unknown} item - The item which ought to be an instance of the constructor.
|
|
207
|
+
* @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
|
|
208
|
+
* @throws {AssertionError} if item instanceof constructor is false.
|
|
209
|
+
*/
|
|
210
|
+
export function assertInstanceOf(item, constructor) {
|
|
70
211
|
if (!import.meta.env.DEV)
|
|
71
212
|
return;
|
|
72
|
-
if (
|
|
73
|
-
throw new AssertionError(`Provided
|
|
213
|
+
if (!(item instanceof constructor))
|
|
214
|
+
throw new AssertionError(`Provided item was not of type ${constructor.name} but was type: ${getTypeNameOfUnknown(item)}, value: ${item}`);
|
|
74
215
|
}
|
|
75
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Asserts that the provided array is a tuple of exactly the expected length.
|
|
218
|
+
* @param {unknown[]} arr - The array which ought to be a tuple.
|
|
219
|
+
* @param {number} expectedLength - The exact expected length of the tuple.
|
|
220
|
+
* @throws {AssertionError} if the array isn't of the expected length or is sparse.
|
|
221
|
+
*/
|
|
222
|
+
export function assertIsTuple(arr, expectedLength) {
|
|
76
223
|
if (!import.meta.env.DEV)
|
|
77
224
|
return;
|
|
78
|
-
if (
|
|
79
|
-
throw new AssertionError(`Provided
|
|
225
|
+
if (arr.length !== expectedLength) {
|
|
226
|
+
throw new AssertionError(`Provided array is not a tuple of expected length ${expectedLength}. It has length ${arr.length}.`);
|
|
227
|
+
}
|
|
228
|
+
for (let i = 0; i < expectedLength; i++) {
|
|
229
|
+
if (!(i in arr))
|
|
230
|
+
throw new AssertionError(`Provided tuple is sparse and therefore not a tuple. Index ${i} is missing.`);
|
|
231
|
+
}
|
|
80
232
|
}
|
|
81
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Used to assert that code can never be reached. Pass a value which has already been checked for all types that should be possible. If the range of possible values increases, TypeScript will throw an error at compile time because the value won't be of type never.
|
|
235
|
+
* @param {never} item - An exhausted value, of which all cases are accounted for in other branches of the code, such as at the end of a switch statement.
|
|
236
|
+
* @param {string} msg - Override the default error message. Even if you do, the error message will include the value and type of item.
|
|
237
|
+
* @throws {AssertionError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
|
|
238
|
+
*/
|
|
239
|
+
export function assertUnreachable(item, msg = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere.") {
|
|
82
240
|
if (!import.meta.env.DEV)
|
|
83
241
|
return;
|
|
84
|
-
|
|
85
|
-
|
|
242
|
+
throw new AssertionError(msg +
|
|
243
|
+
`\nValue of type never was actually of type: ${getTypeNameOfUnknown(item)}, value: ${item}`);
|
|
86
244
|
}
|
|
87
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Asserts that the provided item is neither null nor undefined.
|
|
247
|
+
* @param {unknown} item - The item which ought to be non-null.
|
|
248
|
+
* @throws {AssertionError} if the item is null or undefined.
|
|
249
|
+
*/
|
|
250
|
+
export function assertNonNullable(item) {
|
|
88
251
|
if (!import.meta.env.DEV)
|
|
89
252
|
return;
|
|
90
|
-
|
|
253
|
+
if (item === undefined || item === null)
|
|
254
|
+
throw new AssertionError(`Provided item should've been non-null but was: ${item}`);
|
|
91
255
|
}
|
|
92
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
|
|
258
|
+
* @param {object} obj - The object which ought to have the properties.
|
|
259
|
+
* @param {NullableKeys<T>} propKeys - An array of the stringified keys of the properties which ought to be non-null in the object.
|
|
260
|
+
* @throws {AssertionError} if any of the properties was null, undefined, or not present in the object.
|
|
261
|
+
*/
|
|
262
|
+
export function assertPropsNonNullable(obj, propKeys) {
|
|
93
263
|
if (!import.meta.env.DEV)
|
|
94
264
|
return;
|
|
95
|
-
for (const
|
|
96
|
-
if (
|
|
97
|
-
throw new AssertionError(`Provided object prop ${String(
|
|
265
|
+
for (const propKey of propKeys) {
|
|
266
|
+
if (!(propKey in obj))
|
|
267
|
+
throw new AssertionError(`Provided object prop ${String(propKey)} should've been non-null but was not present at all.`);
|
|
268
|
+
if (obj[propKey] === null || obj[propKey] === undefined)
|
|
269
|
+
throw new AssertionError(`Provided object prop ${String(propKey)} should've been non-null but was: ${obj[propKey]}`);
|
|
98
270
|
}
|
|
99
271
|
}
|
|
100
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Asserts that all elements of the provided array are neither null nor undefined, or not present.
|
|
274
|
+
* @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements.
|
|
275
|
+
* @throws {AssertionError} if any of the elements was null, undefined, or not present in the array.
|
|
276
|
+
*/
|
|
277
|
+
export function assertArrayNonNullable(arr) {
|
|
101
278
|
if (!import.meta.env.DEV)
|
|
102
279
|
return;
|
|
103
|
-
|
|
104
|
-
|
|
280
|
+
for (let i = 0; i < arr.length; i++) {
|
|
281
|
+
if (!(i in arr))
|
|
282
|
+
throw new AssertionError(`Provided array should've been non-null but was sparse with a missing item at index ${i}`);
|
|
283
|
+
const item = arr[i];
|
|
284
|
+
if (item === null)
|
|
285
|
+
throw new AssertionError(`Provided array should've been non-null but had an item with value null at index ${i}`);
|
|
286
|
+
if (item === undefined)
|
|
287
|
+
throw new AssertionError(`Provided array should've been non-null but had an undefined item at index ${i}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Asserts that the provided tuple has non-null values for all elements. This function does not take a length. So if you want to assert that the typescript tuple type is of the correct length, call @see assertIsTuple first.
|
|
292
|
+
* @param {[unknown, ...]} tuple - The tuple which ought to have only non-null values.
|
|
293
|
+
* @throws {AssertionError} if any of the elements was null, undefined, or an index not present in the tuple.
|
|
294
|
+
*/
|
|
295
|
+
export function assertTupleNonNullable(tuple) {
|
|
296
|
+
if (!import.meta.env.DEV)
|
|
297
|
+
return;
|
|
298
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
299
|
+
if (!(i in tuple))
|
|
300
|
+
throw new AssertionError(`Provided tuple should've been non-null but is sparse. Index ${i} is missing.`);
|
|
301
|
+
if (tuple[i] === null)
|
|
302
|
+
throw new AssertionError(`Provided tuple should've been non-null but had an item with value null at index ${i}`);
|
|
303
|
+
if (tuple[i] === undefined)
|
|
304
|
+
throw new AssertionError(`Provided tuple should've been non-null but had an undefined item at index ${i}`);
|
|
305
|
+
}
|
|
105
306
|
}
|
|
106
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Asserts that the provided item is a finite number. Use to prevent NaN propagation.
|
|
309
|
+
* @param {unknown} item - The item which ought to be a finite number.
|
|
310
|
+
* @throws {AssertionError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
|
|
311
|
+
*/
|
|
312
|
+
export function assertFiniteNumber(item) {
|
|
107
313
|
if (!import.meta.env.DEV)
|
|
108
314
|
return;
|
|
109
|
-
if (typeof
|
|
110
|
-
throw new AssertionError(`Provided
|
|
111
|
-
if (!isFinite(
|
|
112
|
-
throw new AssertionError(`Provided number was not finite. Was: ${
|
|
315
|
+
if (typeof item !== "number")
|
|
316
|
+
throw new AssertionError(`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`);
|
|
317
|
+
if (!isFinite(item))
|
|
318
|
+
throw new AssertionError(`Provided number was not finite. Was: ${item}`);
|
|
113
319
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assertie",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Debug assertions for TypeScript, auto tree-shaken by vite for production.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"TypeScript",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"typescript": ">=
|
|
32
|
+
"typescript": ">=4.7.0",
|
|
33
33
|
"vite": ">=2.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"typescript": "
|
|
37
|
-
"vite": "
|
|
36
|
+
"typescript": "~4.7.0",
|
|
37
|
+
"vite": "~2.0.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc"
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
// Creates a tuple of length N with all elements of type T
|
|
2
|
+
type Tuple<T, N extends number, A extends unknown[] = []> =
|
|
3
|
+
A["length"] extends N ? A : Tuple<T, N, [...A, T]>;
|
|
4
|
+
|
|
2
5
|
type PrimitiveTypes = {
|
|
3
|
-
"string": string
|
|
4
|
-
"number": number
|
|
5
|
-
"boolean": boolean
|
|
6
|
-
"bigint": bigint
|
|
7
|
-
"undefined": undefined
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type NullableKeys<T> = {
|
|
15
|
-
[K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never
|
|
6
|
+
"string": string;
|
|
7
|
+
"number": number;
|
|
8
|
+
"boolean": boolean;
|
|
9
|
+
"bigint": bigint;
|
|
10
|
+
"undefined": undefined;
|
|
11
|
+
"function": Function;
|
|
12
|
+
"object": object;
|
|
13
|
+
"symbol": symbol;
|
|
14
|
+
};
|
|
15
|
+
type PrimitiveTypeStrings = keyof PrimitiveTypes;
|
|
16
|
+
|
|
17
|
+
type NullableKeys<T> = {
|
|
18
|
+
[K in keyof T]-?: undefined extends T[K] ? K : null extends T[K] ? K : never;
|
|
16
19
|
}[keyof T];
|
|
17
20
|
|
|
18
|
-
type PropsNonNullable<T, N extends NullableKeys<T>> =
|
|
21
|
+
type PropsNonNullable<T, N extends NullableKeys<T>> = T & {
|
|
22
|
+
[K in N]-?: NonNullable<T[K]>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Constructor<T> = new (...args: unknown[]) => T;
|
|
26
|
+
|
|
27
|
+
type AllJSTypes = PrimitiveTypeStrings | null | undefined | Constructor<unknown>;
|
|
28
|
+
|
|
29
|
+
type ResolveAnyJSType<T extends AllJSTypes> = T extends
|
|
30
|
+
| PrimitiveTypeStrings ? PrimitiveTypes[T]
|
|
31
|
+
: T extends null ? null
|
|
32
|
+
: T extends undefined ? undefined
|
|
33
|
+
: T extends Constructor<infer U> ? U
|
|
34
|
+
: never;
|
|
35
|
+
|
|
36
|
+
function getNameOfExpectedType(expectedType: AllJSTypes): string {
|
|
37
|
+
if (expectedType === null) return "null";
|
|
38
|
+
if (expectedType === undefined) return "undefined";
|
|
39
|
+
if (typeof expectedType === "string") return expectedType;
|
|
40
|
+
return expectedType.name;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getTypeNameOfUnknown(item: unknown): string {
|
|
44
|
+
if (item === null) return "null";
|
|
45
|
+
if (item === undefined) return "undefined";
|
|
46
|
+
try {
|
|
47
|
+
if (item instanceof item.constructor && item.constructor.name !== "Function") {
|
|
48
|
+
// I'd like function to match the primitive name "function"
|
|
49
|
+
// because that's how the asserts are written.
|
|
50
|
+
return item.constructor.name;
|
|
51
|
+
}
|
|
52
|
+
} finally {
|
|
53
|
+
return typeof item;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isType<T extends AllJSTypes>(item: unknown, expectedType: T): item is ResolveAnyJSType<T> {
|
|
58
|
+
if (typeof item === expectedType) return true;
|
|
59
|
+
const reducedExpectedType = expectedType as Exclude<typeof expectedType, PrimitiveTypeStrings>;
|
|
60
|
+
|
|
61
|
+
if (item === reducedExpectedType) return true;
|
|
62
|
+
const remainingOption = expectedType as Exclude<typeof reducedExpectedType, null | undefined>;
|
|
63
|
+
|
|
64
|
+
if (item instanceof remainingOption) return true;
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
19
68
|
|
|
20
69
|
class AssertionError extends Error {
|
|
21
70
|
constructor(msg: string) {
|
|
@@ -24,103 +73,376 @@ class AssertionError extends Error {
|
|
|
24
73
|
}
|
|
25
74
|
}
|
|
26
75
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Asserts that the provided boolean is true.
|
|
78
|
+
* @param {boolean} hasToBeTrue - The boolean to assert.
|
|
79
|
+
* @param {string} msg - The message of the Error if the assertion fails.
|
|
80
|
+
* @throws {AssertionError} if the assertion fails.
|
|
81
|
+
*/
|
|
82
|
+
export function assert(
|
|
83
|
+
hasToBeTrue: boolean,
|
|
84
|
+
msg: string = "No specific message provided."
|
|
85
|
+
): asserts hasToBeTrue is true {
|
|
86
|
+
if (!import.meta.env.DEV) return;
|
|
87
|
+
if (!hasToBeTrue) throw new AssertionError(msg);
|
|
88
|
+
}
|
|
32
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Asserts that the provided object is of the expectedType.
|
|
92
|
+
* @param {unknown} item - The object which ought to be of the expectedType.
|
|
93
|
+
* @param {AllJSTypes} expectedType - The expected type of the object. JS primitive types, null, undefined, and constructable types are supported. JS primitive types are passed as the string they return from typeof, e.g., "number".
|
|
94
|
+
* @throws {AssertionError} if the type isn't as expected.
|
|
95
|
+
*/
|
|
96
|
+
export function assertType<T extends AllJSTypes>(
|
|
97
|
+
item: unknown,
|
|
98
|
+
expectedType: T
|
|
99
|
+
): asserts item is ResolveAnyJSType<T> {
|
|
100
|
+
if (!import.meta.env.DEV) return;
|
|
101
|
+
if (!isType(item, expectedType))
|
|
102
|
+
throw new AssertionError(
|
|
103
|
+
`Provided object was not of type ${getNameOfExpectedType(
|
|
104
|
+
expectedType
|
|
105
|
+
)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
33
108
|
|
|
34
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Asserts that all elements of the provided array are of the expected type. It ensures that the array is not sparse (even when the expectedType is undefined).
|
|
111
|
+
* @param {unknown[]} arr - The array which ought to be an array of the expectedType, i.e. expectedType: "number" => arr: number[]
|
|
112
|
+
* @param {AllJSTypes} expectedType - The expected type of individual items. JS primitive types, null, undefined, and constructable types are supported.
|
|
113
|
+
* @throws {AssertionError} if the type isn't as expected.
|
|
114
|
+
*/
|
|
115
|
+
export function assertArrayType<T extends AllJSTypes>(
|
|
116
|
+
arr: unknown[],
|
|
117
|
+
expectedType: T
|
|
118
|
+
): asserts arr is ResolveAnyJSType<T>[] {
|
|
35
119
|
if (!import.meta.env.DEV) return;
|
|
36
|
-
|
|
120
|
+
for (let i = 0; i < arr.length; i++) {
|
|
121
|
+
if (!(i in arr))
|
|
122
|
+
throw new AssertionError(
|
|
123
|
+
`Array to assert type of was sparse with a missing item at index ${i}`
|
|
124
|
+
);
|
|
125
|
+
const item = arr[i];
|
|
126
|
+
if (!isType(item, expectedType))
|
|
127
|
+
throw new AssertionError(
|
|
128
|
+
`Provided array had item at index ${i} not of type ${getNameOfExpectedType(
|
|
129
|
+
expectedType
|
|
130
|
+
)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
37
133
|
}
|
|
38
134
|
|
|
39
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Asserts that the array or tuple has the expected types at each index.
|
|
137
|
+
* @param {unknown[] | [unknown, ...]} arrayOrTuple - The tuple which ought to be an array of the length and types.
|
|
138
|
+
* @param {[AllJSTypes, ...]} expectedTypes - A tuple of expected types of individual items, e.g., expectedTypes = ["number", "string", Date] => arrayOrTuple: [number, string, Date]. The individual entries can be JS primitive types, null, undefined, and constructors.
|
|
139
|
+
* @throws {AssertionError} if the type of any element of the tuple isn't as expected.
|
|
140
|
+
*/
|
|
141
|
+
export function assertTupleTypes<
|
|
142
|
+
T extends readonly AllJSTypes[],
|
|
143
|
+
U extends
|
|
144
|
+
| { [K in keyof T]: unknown } // [...unknown] matching length of [...T]
|
|
145
|
+
| (number extends U["length"] ? unknown[] : never) // Array with compile time unknown length
|
|
146
|
+
>(
|
|
147
|
+
arrayOrTuple: U,
|
|
148
|
+
expectedTypes: readonly [...T]
|
|
149
|
+
): asserts arrayOrTuple is U & { [K in keyof T]: ResolveAnyJSType<T[K]> } {
|
|
40
150
|
if (!import.meta.env.DEV) return;
|
|
151
|
+
if (arrayOrTuple.length !== expectedTypes.length) {
|
|
152
|
+
throw new AssertionError(
|
|
153
|
+
`Provided tuple length mismatch: expected ${expectedTypes.length}, but got ${arrayOrTuple.length}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
for (let i = 0; i < expectedTypes.length; i++) {
|
|
157
|
+
if (!(i in arrayOrTuple))
|
|
158
|
+
throw new AssertionError(
|
|
159
|
+
`Provided tuple was sparse with a missing item at required index ${i}`
|
|
160
|
+
);
|
|
161
|
+
const item = arrayOrTuple[i];
|
|
162
|
+
if (!isType(item, expectedTypes[i])) {
|
|
163
|
+
throw new AssertionError(
|
|
164
|
+
`Provided tuple had item at index ${i} not of type ${getNameOfExpectedType(
|
|
165
|
+
expectedTypes[i]
|
|
166
|
+
)}. Was: ${getTypeNameOfUnknown(item)}, value: ${item}`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
41
171
|
|
|
42
|
-
|
|
43
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Asserts that the provided item is of type string.
|
|
174
|
+
* @param {unknown} item - The item which ought to be of type string.
|
|
175
|
+
* @throws {AssertionError} if the type isn't string.
|
|
176
|
+
*/
|
|
177
|
+
export function assertTypeOfString(item: unknown): asserts item is string {
|
|
178
|
+
if (!import.meta.env.DEV) return;
|
|
179
|
+
if (typeof item !== "string")
|
|
180
|
+
throw new AssertionError(
|
|
181
|
+
`Provided item was not of type string. Was: ${getTypeNameOfUnknown(item)}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
44
184
|
|
|
45
|
-
|
|
46
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Asserts that the provided item is of type number.
|
|
187
|
+
* @param {unknown} item - The item which ought to be of type number.
|
|
188
|
+
* @throws {AssertionError} if the type isn't number.
|
|
189
|
+
*/
|
|
190
|
+
export function assertTypeOfNumber(item: unknown): asserts item is number {
|
|
191
|
+
if (!import.meta.env.DEV) return;
|
|
192
|
+
if (typeof item !== "number")
|
|
193
|
+
throw new AssertionError(
|
|
194
|
+
`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
47
197
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Asserts that the provided item is of type boolean.
|
|
200
|
+
* @param {unknown} item - The item which ought to be of type boolean.
|
|
201
|
+
* @throws {AssertionError} if the type isn't boolean.
|
|
202
|
+
*/
|
|
203
|
+
export function assertTypeOfBoolean(item: unknown): asserts item is boolean {
|
|
204
|
+
if (!import.meta.env.DEV) return;
|
|
205
|
+
if (typeof item !== "boolean")
|
|
206
|
+
throw new AssertionError(
|
|
207
|
+
`Provided item was not of type boolean. Was: ${getTypeNameOfUnknown(item)}`
|
|
208
|
+
);
|
|
51
209
|
}
|
|
52
210
|
|
|
53
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Asserts that the provided item is of type bigint.
|
|
213
|
+
* @param {unknown} item - The item which ought to be of type bigint.
|
|
214
|
+
* @throws {AssertionError} if the type isn't bigint.
|
|
215
|
+
*/
|
|
216
|
+
export function assertTypeOfBigint(item: unknown): asserts item is bigint {
|
|
54
217
|
if (!import.meta.env.DEV) return;
|
|
55
|
-
if (typeof
|
|
218
|
+
if (typeof item !== "bigint")
|
|
219
|
+
throw new AssertionError(
|
|
220
|
+
`Provided item was not of type bigint. Was: ${getTypeNameOfUnknown(item)}`
|
|
221
|
+
);
|
|
56
222
|
}
|
|
57
223
|
|
|
58
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Asserts that the provided item is of type undefined.
|
|
226
|
+
* @param {unknown} item - The item which ought to be of type undefined.
|
|
227
|
+
* @throws {AssertionError} if the type isn't undefined.
|
|
228
|
+
*/
|
|
229
|
+
export function assertTypeOfUndefined(item: unknown): asserts item is undefined {
|
|
59
230
|
if (!import.meta.env.DEV) return;
|
|
60
|
-
if (typeof
|
|
231
|
+
if (typeof item !== "undefined")
|
|
232
|
+
throw new AssertionError(
|
|
233
|
+
`Provided item was not of type undefined. Was: ${getTypeNameOfUnknown(item)}`
|
|
234
|
+
);
|
|
61
235
|
}
|
|
62
236
|
|
|
63
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Asserts that the provided item is of type function.
|
|
239
|
+
* @param {unknown} item - The item which ought to be of type function.
|
|
240
|
+
* @throws {AssertionError} if the type isn't function.
|
|
241
|
+
*/
|
|
242
|
+
export function assertTypeOfFunction(item: unknown): asserts item is Function {
|
|
64
243
|
if (!import.meta.env.DEV) return;
|
|
65
|
-
if (typeof
|
|
244
|
+
if (typeof item !== "function")
|
|
245
|
+
throw new AssertionError(
|
|
246
|
+
`Provided item was not of type function. Was: ${getTypeNameOfUnknown(item)}`
|
|
247
|
+
);
|
|
66
248
|
}
|
|
67
249
|
|
|
68
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Asserts that the provided item is of type object.
|
|
252
|
+
* @param {unknown} item - The item which ought to be of type object.
|
|
253
|
+
* @throws {AssertionError} if the type isn't object.
|
|
254
|
+
*/
|
|
255
|
+
export function assertTypeOfObject(item: unknown): asserts item is object {
|
|
69
256
|
if (!import.meta.env.DEV) return;
|
|
70
|
-
if (typeof
|
|
257
|
+
if (typeof item !== "object")
|
|
258
|
+
throw new AssertionError(
|
|
259
|
+
`Provided item was not of type object. Was: ${getTypeNameOfUnknown(item)}`
|
|
260
|
+
);
|
|
71
261
|
}
|
|
72
262
|
|
|
73
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Asserts that the provided item is of type symbol.
|
|
265
|
+
* @param {unknown} item - The item which ought to be of type symbol.
|
|
266
|
+
* @throws {AssertionError} if the type isn't symbol.
|
|
267
|
+
*/
|
|
268
|
+
export function assertTypeOfSymbol(item: unknown): asserts item is symbol {
|
|
74
269
|
if (!import.meta.env.DEV) return;
|
|
75
|
-
if (typeof
|
|
270
|
+
if (typeof item !== "symbol")
|
|
271
|
+
throw new AssertionError(
|
|
272
|
+
`Provided item was not of type symbol. Was: ${getTypeNameOfUnknown(item)}`
|
|
273
|
+
);
|
|
76
274
|
}
|
|
77
275
|
|
|
78
|
-
|
|
79
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Asserts that the provided item is null.
|
|
278
|
+
* @param {unknown} item - The item which ought to be null.
|
|
279
|
+
* @throws {AssertionError} if the value isn't null.
|
|
280
|
+
*/
|
|
281
|
+
export function assertNull(item: unknown): asserts item is null {
|
|
80
282
|
if (!import.meta.env.DEV) return;
|
|
81
|
-
if (
|
|
283
|
+
if (item !== null)
|
|
284
|
+
throw new AssertionError(
|
|
285
|
+
`Provided item was not null. Was type: ${getTypeNameOfUnknown(item)}, value: ${item}`
|
|
286
|
+
);
|
|
82
287
|
}
|
|
83
288
|
|
|
84
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Asserts that the provided item is an instance of the provided constructor.
|
|
291
|
+
* @param {unknown} item - The item which ought to be an instance of the constructor.
|
|
292
|
+
* @param {Constructor<T>} constructor - Anything that can be after an instanceof operator.
|
|
293
|
+
* @throws {AssertionError} if item instanceof constructor is false.
|
|
294
|
+
*/
|
|
295
|
+
export function assertInstanceOf<T>(item: unknown, constructor: Constructor<T>): asserts item is T {
|
|
85
296
|
if (!import.meta.env.DEV) return;
|
|
86
|
-
if (
|
|
297
|
+
if (!(item instanceof constructor))
|
|
298
|
+
throw new AssertionError(
|
|
299
|
+
`Provided item was not of type ${constructor.name} but was type: ${getTypeNameOfUnknown(
|
|
300
|
+
item
|
|
301
|
+
)}, value: ${item}`
|
|
302
|
+
);
|
|
87
303
|
}
|
|
88
304
|
|
|
89
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Asserts that the provided array is a tuple of exactly the expected length.
|
|
307
|
+
* @param {unknown[]} arr - The array which ought to be a tuple.
|
|
308
|
+
* @param {number} expectedLength - The exact expected length of the tuple.
|
|
309
|
+
* @throws {AssertionError} if the array isn't of the expected length or is sparse.
|
|
310
|
+
*/
|
|
311
|
+
export function assertIsTuple<
|
|
312
|
+
T extends number extends T["length"] ? unknown[] : never,
|
|
313
|
+
N extends number
|
|
314
|
+
>(arr: [...T], expectedLength: N): asserts arr is T & Tuple<T[number], N> {
|
|
90
315
|
if (!import.meta.env.DEV) return;
|
|
91
|
-
if (
|
|
316
|
+
if (arr.length !== expectedLength) {
|
|
317
|
+
throw new AssertionError(
|
|
318
|
+
`Provided array is not a tuple of expected length ${expectedLength}. It has length ${arr.length}.`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
for (let i = 0; i < expectedLength; i++) {
|
|
322
|
+
if (!(i in arr))
|
|
323
|
+
throw new AssertionError(
|
|
324
|
+
`Provided tuple is sparse and therefore not a tuple. Index ${i} is missing.`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
92
327
|
}
|
|
93
328
|
|
|
94
|
-
|
|
329
|
+
/**
|
|
330
|
+
* Used to assert that code can never be reached. Pass a value which has already been checked for all types that should be possible. If the range of possible values increases, TypeScript will throw an error at compile time because the value won't be of type never.
|
|
331
|
+
* @param {never} item - An exhausted value, of which all cases are accounted for in other branches of the code, such as at the end of a switch statement.
|
|
332
|
+
* @param {string} msg - Override the default error message. Even if you do, the error message will include the value and type of item.
|
|
333
|
+
* @throws {AssertionError} if at runtime the function call was reached. This should only happen if TypeScript types are inaccurate somewhere.
|
|
334
|
+
*/
|
|
335
|
+
export function assertUnreachable(
|
|
336
|
+
item: never,
|
|
337
|
+
msg: string = "Unreachable code of type never was reached. TypeScript types are inaccurate somewhere."
|
|
338
|
+
): asserts item is never {
|
|
95
339
|
if (!import.meta.env.DEV) return;
|
|
96
|
-
|
|
340
|
+
throw new AssertionError(
|
|
341
|
+
msg +
|
|
342
|
+
`\nValue of type never was actually of type: ${getTypeNameOfUnknown(
|
|
343
|
+
item
|
|
344
|
+
)}, value: ${item}`
|
|
345
|
+
);
|
|
97
346
|
}
|
|
98
347
|
|
|
99
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Asserts that the provided item is neither null nor undefined.
|
|
350
|
+
* @param {unknown} item - The item which ought to be non-null.
|
|
351
|
+
* @throws {AssertionError} if the item is null or undefined.
|
|
352
|
+
*/
|
|
353
|
+
export function assertNonNullable<T>(item: T): asserts item is NonNullable<T> {
|
|
100
354
|
if (!import.meta.env.DEV) return;
|
|
101
|
-
if (
|
|
355
|
+
if (item === undefined || item === null)
|
|
356
|
+
throw new AssertionError(`Provided item should've been non-null but was: ${item}`);
|
|
102
357
|
}
|
|
103
358
|
|
|
104
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Asserts that the provided object has non-null values for the properties passed as keys in the propKeys array.
|
|
361
|
+
* @param {object} obj - The object which ought to have the properties.
|
|
362
|
+
* @param {NullableKeys<T>} propKeys - An array of the stringified keys of the properties which ought to be non-null in the object.
|
|
363
|
+
* @throws {AssertionError} if any of the properties was null, undefined, or not present in the object.
|
|
364
|
+
*/
|
|
365
|
+
export function assertPropsNonNullable<T extends object, N extends NullableKeys<T>>(
|
|
366
|
+
obj: T,
|
|
367
|
+
propKeys: N[]
|
|
368
|
+
): asserts obj is PropsNonNullable<T, N> {
|
|
105
369
|
if (!import.meta.env.DEV) return;
|
|
106
|
-
|
|
370
|
+
for (const propKey of propKeys) {
|
|
371
|
+
if (!(propKey in obj))
|
|
372
|
+
throw new AssertionError(
|
|
373
|
+
`Provided object prop ${String(
|
|
374
|
+
propKey
|
|
375
|
+
)} should've been non-null but was not present at all.`
|
|
376
|
+
);
|
|
377
|
+
if (obj[propKey] === null || obj[propKey] === undefined)
|
|
378
|
+
throw new AssertionError(
|
|
379
|
+
`Provided object prop ${String(propKey)} should've been non-null but was: ${
|
|
380
|
+
obj[propKey]
|
|
381
|
+
}`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
107
384
|
}
|
|
108
385
|
|
|
109
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Asserts that all elements of the provided array are neither null nor undefined, or not present.
|
|
388
|
+
* @param {unknown[]} arr - The array which ought to be non-sparse, and have only non-null elements.
|
|
389
|
+
* @throws {AssertionError} if any of the elements was null, undefined, or not present in the array.
|
|
390
|
+
*/
|
|
391
|
+
export function assertArrayNonNullable<T>(arr: T[]): asserts arr is NonNullable<T>[] {
|
|
110
392
|
if (!import.meta.env.DEV) return;
|
|
111
|
-
for (
|
|
112
|
-
if (
|
|
113
|
-
throw new AssertionError(
|
|
393
|
+
for (let i = 0; i < arr.length; i++) {
|
|
394
|
+
if (!(i in arr))
|
|
395
|
+
throw new AssertionError(
|
|
396
|
+
`Provided array should've been non-null but was sparse with a missing item at index ${i}`
|
|
397
|
+
);
|
|
398
|
+
const item = arr[i];
|
|
399
|
+
if (item === null)
|
|
400
|
+
throw new AssertionError(
|
|
401
|
+
`Provided array should've been non-null but had an item with value null at index ${i}`
|
|
402
|
+
);
|
|
403
|
+
if (item === undefined)
|
|
404
|
+
throw new AssertionError(
|
|
405
|
+
`Provided array should've been non-null but had an undefined item at index ${i}`
|
|
406
|
+
);
|
|
114
407
|
}
|
|
115
408
|
}
|
|
116
409
|
|
|
117
|
-
|
|
410
|
+
/**
|
|
411
|
+
* Asserts that the provided tuple has non-null values for all elements. This function does not take a length. So if you want to assert that the typescript tuple type is of the correct length, call @see assertIsTuple first.
|
|
412
|
+
* @param {[unknown, ...]} tuple - The tuple which ought to have only non-null values.
|
|
413
|
+
* @throws {AssertionError} if any of the elements was null, undefined, or an index not present in the tuple.
|
|
414
|
+
*/
|
|
415
|
+
export function assertTupleNonNullable<T extends number extends T["length"] ? never : unknown[]>(
|
|
416
|
+
tuple: T
|
|
417
|
+
): asserts tuple is { [K in keyof T]: NonNullable<T[K]> } {
|
|
118
418
|
if (!import.meta.env.DEV) return;
|
|
119
|
-
|
|
419
|
+
for (let i = 0; i < tuple.length; i++) {
|
|
420
|
+
if (!(i in tuple))
|
|
421
|
+
throw new AssertionError(
|
|
422
|
+
`Provided tuple should've been non-null but is sparse. Index ${i} is missing.`
|
|
423
|
+
);
|
|
424
|
+
if (tuple[i] === null)
|
|
425
|
+
throw new AssertionError(
|
|
426
|
+
`Provided tuple should've been non-null but had an item with value null at index ${i}`
|
|
427
|
+
);
|
|
428
|
+
if (tuple[i] === undefined)
|
|
429
|
+
throw new AssertionError(
|
|
430
|
+
`Provided tuple should've been non-null but had an undefined item at index ${i}`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
120
433
|
}
|
|
121
434
|
|
|
122
|
-
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Asserts that the provided item is a finite number. Use to prevent NaN propagation.
|
|
438
|
+
* @param {unknown} item - The item which ought to be a finite number.
|
|
439
|
+
* @throws {AssertionError} if the item is not of type number, or isFinite(item) is false, i.e., if the item is NaN, Infinity, or -Infinity.
|
|
440
|
+
*/
|
|
441
|
+
export function assertFiniteNumber(item: unknown): asserts item is number {
|
|
123
442
|
if (!import.meta.env.DEV) return;
|
|
124
|
-
if (typeof
|
|
125
|
-
|
|
443
|
+
if (typeof item !== "number")
|
|
444
|
+
throw new AssertionError(
|
|
445
|
+
`Provided item was not of type number. Was: ${getTypeNameOfUnknown(item)}`
|
|
446
|
+
);
|
|
447
|
+
if (!isFinite(item)) throw new AssertionError(`Provided number was not finite. Was: ${item}`);
|
|
126
448
|
}
|