-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.ts
276 lines (257 loc) · 7.58 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/**
* An error thrown by a code contract.
*/
export abstract class ContractError extends Error {}
/**
* An error thrown, if a precondition for a function or method is not met.
*/
export class PreconditionError extends ContractError {
constructor(message?: string) {
super(message);
this.name = 'PreconditionError';
}
}
/**
* An error thrown, if an object is an illegal state.
*/
export class IllegalStateError extends ContractError {
constructor(message?: string) {
super(message);
this.name = 'IllegalStateError';
}
}
/**
* An error thrown, if a function or method could not fulfil a postcondition.
*/
export class PostconditionError extends ContractError {
constructor(message?: string) {
super(message);
this.name = 'PostconditionError';
}
}
/**
* An error thrown, if an assertion has failed.
*/
export class AssertionError extends ContractError {
constructor(message?: string) {
super(message);
this.name = 'AssertionError';
}
}
/**
* Throws a `PreconditionError` if the `condition` is `false`.
* @param condition the precondition that should be `true`
* @param message an optional message for the error
* @throws PreconditionError if the condition is `false`
* @see PreconditionError
* @example
* function myFun(name: string) {
* requires(name.length > 10, 'Name must be longer than 10 chars');
* }
*/
export function requires(
condition: boolean,
message = 'Unmet precondition'
): asserts condition {
if (!condition) {
throw new PreconditionError(message);
}
}
/**
* Returns the given value unchanged if it is not `null` or `undefined`.
* Throws a `PreconditionError` otherwise.
* @param value the value that should not be `null` or `undefined`
* @param message an optional message for the error
* @throws PreconditionError if the value is `null` or `undefined`
* @see requires
* @example
* function myFun(name: string | null) {
* const nameNonNull = requiresNonNullish(name, 'Name must be defined');
* nameNonNull.toUpperCase(); // no compiler error!
* }
*/
export function requiresNonNullish<T>(
value: T,
message = 'Value must not be null or undefined'
): NonNullable<T> {
requires(isDefined(value), message);
return value;
}
/**
* Throws a `IllegalStateError` if the `condition` is `false`.
* @param condition the condition that should be `true`
* @param message an optional message for the error
* @throws IllegalStateError if the condition is `false`
* @see IllegalStateError
* @example
* class Socket {
* private isOpen = false;
* send(data: Data) {
* check(this.isOpen, 'Socket must be open');
* }
* open() {
* this.isOpen = true;
* }
* }
*/
export function checks(
condition: boolean,
message = 'Callee invariant violation'
): asserts condition {
if (!condition) {
throw new IllegalStateError(message);
}
}
/**
* Returns the given value unchanged if it is not `null` or `undefined`.
* Throws a `IllegalStateError` otherwise.
* @param value the value that should not be `null` or `undefined`
* @param message an optional message for the error
* @throws IllegalStateError if the value is `null` or `undefined`
* @see checks
* @example
* class Socket {
* data: Data | null = null;
* send() {
* const validData = checksNonNullish(this.data, 'Data must be available');
* validData.send(); // no compiler error!
* }
* }
*/
export function checksNonNullish<T>(
value: T,
message = 'Value must not be null or undefined'
): NonNullable<T> {
checks(isDefined(value), message);
return value;
}
/**
* Throws a `PostconditionError` if the `condition` is `false`.
* @param condition the condition that should be `true`
* @param message an optional message for the error
* @throws PostconditionError if the condition is `false`
* @see PostconditionError
* @example
* function myFun() {
* createPerson({ id: 0, name: 'John' });
* const entity = findById(0); // returns null if not present
* return ensures(isDefined(entity), 'Failed to persist entity');
* }
*/
export function ensures(
condition: boolean,
message = 'Unmet postcondition'
): asserts condition {
if (!condition) {
throw new PostconditionError(message);
}
}
/**
* Returns the given value unchanged if it is not `null` or `undefined`.
* Throws a `PostconditionError` otherwise.
* @param value the value that must not be `null` or `undefined`
* @param message an optional message for the error
* @throws PostconditionError if the value is `null` or `undefined`
* @see ensures
* @example
* function myFun(): Person {
* createPerson({ id: 0, name: 'John' });
* const entity = findById(0); // returns null if not present
* return ensuresNonNullish(entity, 'Failed to persist entity');
* }
*/
export function ensuresNonNullish<T>(
value: T,
message = 'Value must not be null or undefined'
): NonNullable<T> {
ensures(isDefined(value), message);
return value;
}
/**
* Throws a `AssertionError` if the `condition` is `false`.
* @param condition the condition that must be `true`
* @param message an optional message for the error
* @throws AssertionError if the condition is `false`
* @see AssertionError
*/
export function asserts(
condition: boolean,
message?: string
): asserts condition {
if (!condition) {
throw new AssertionError(message);
}
}
/**
* Returns `true` if the value is not `null` or `undefined`.
* @param value the value to test
* @example
* const x: string | null = 'Hello';
* if (isDefined(x)) {
* x.toLowerCase(); // no compiler error!
* }
*/
export function isDefined<T>(value: T): value is NonNullable<T> {
return value != null;
}
/* eslint-disable @typescript-eslint/no-explicit-any, new-cap */
/**
* Always throws an `IllegalStateError` with the given message.
* @param message the message for the `IllegalStateError`
* @throws IllegalStateError in any case
* @see IllegalStateError
* @example
* function myFun(foo: string | null) {
* const bar = foo ?? error(PreconditionError, 'Argument may not be null');
* const result = bar.length > 0 ? 'OK' : error('Something went wrong!');
* }
*/
export function error(message?: string): never;
/**
* Always throws an error of the given type with the given message.
* @param errorType an error class
* @param message the error message
* @throws errorType in any case
* @see IllegalStateError
* @example
* function myFun(foo: string | null) {
* const bar = foo ?? error(PreconditionError, 'Argument may not be null');
* const result = bar.length > 0 ? 'OK' : error('Something went wrong!');
* }
*/
export function error(
errorType: new (...args: any[]) => Error,
message?: string
): never;
export function error(
errorType?: string | (new (...args: any[]) => Error),
message?: string
): never {
throw errorType == null || typeof errorType === 'string'
? new IllegalStateError(errorType)
: new errorType(message);
}
/* eslint-enable @typescript-eslint/no-explicit-any, new-cap */
/* eslint-disable @typescript-eslint/no-unused-vars */
/**
* Asserts that a code branch is unreachable. If it is, the compiler will throw a type error.
* If this function is reached at runtime, an error will be thrown.
* @param value a value
* @param message an optional message for the error
* @throws AssertionError in any case
* @example
* function myFun(foo: MyEnum): string {
* switch(foo) {
* case MyEnum.A: return 'a';
* case MyEnum.B: return 'b';
* // no compiler error if MyEnum only has A and B
* default: unreachable(foo);
* }
* }
*/
export function unreachable(
value: never,
message = 'Reached an unreachable case'
): never {
throw new AssertionError(message);
}