Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add assertion utils #33

Merged
merged 1 commit into from
Oct 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/assert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { assert, assertExhaustive, AssertionError } from './assert';

describe('assert', () => {
it('succeeds', () => {
expect(() => assert(true)).not.toThrow();
});

it('narrows type scope', () => {
const item: { foo: 1 } | undefined = { foo: 1 };

assert(item !== undefined);

// This will fail to compile otherwise
expect(item.foo).toStrictEqual(1);
});

it('throws', () => {
expect(() => assert(false, 'Thrown')).toThrow(AssertionError);
});

it('throws with default message', () => {
expect(() => assert(false)).toThrow('Assertion failed.');
});

it('throw custom error', () => {
class MyError extends Error {}
expect(() => assert(false, new MyError('Thrown'))).toThrow(MyError);
});
});

describe('assertExhaustive', () => {
it('throws', () => {
expect(() => assertExhaustive('foo' as never)).toThrow(
'Invalid branch reached. Should be detected during compilation.',
);
});
});
50 changes: 50 additions & 0 deletions src/assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export class AssertionError extends Error {
readonly code = 'ERR_ASSERTION';

constructor(options: { message: string }) {
super(options.message);
}
}

/**
* Same as Node.js assert.
* If the value is falsy, throws an error, does nothing otherwise.
*
* @throws {@link AssertionError}. If value is falsy.
* @param value - The test that should be truthy to pass.
* @param message - Message to be passed to {@link AssertionError} or an
* {@link Error} instance to throw.
*/
export function assert(value: any, message?: string | Error): asserts value {
if (!value) {
if (message instanceof Error) {
throw message;
}
throw new AssertionError({ message: message ?? 'Assertion failed.' });
}
}

/**
* Use in the default case of a switch that you want to be fully exhaustive.
* Using this function forces the compiler to enforce exhaustivity during
* compile-time.
*
* @example
* ```
* const number = 1;
* switch (number) {
* case 0:
* ...
* case 1:
* ...
* default:
* assertExhaustive(snapPrefix);
* }
* ```
* @param _object - The object on which the switch is being operated.
*/
export function assertExhaustive(_object: never): never {
throw new Error(
'Invalid branch reached. Should be detected during compilation.',
);
}