Skip to content

Commit

Permalink
feat: assertion functions (#107)
Browse files Browse the repository at this point in the history
* feat: core util package (WIP)

Signed-off-by: Wouter Termont <[email protected]>

* feat: initial assert functions

Signed-off-by: Wouter Termont <[email protected]>

* test: test initial assert functions

Signed-off-by: Wouter Termont <[email protected]>

* chore: docs & cleanup

Signed-off-by: Wouter Termont <[email protected]>
  • Loading branch information
woutermont authored Nov 30, 2021
1 parent 9d4b23b commit d54cb75
Show file tree
Hide file tree
Showing 10 changed files with 6,156 additions and 0 deletions.
4 changes: 4 additions & 0 deletions dgt-shared.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
{
"name": "dgt-utils",
"path": "packages/dgt-utils"
},
{
"name": "dgt-utils-core",
"path": "packages/dgt-utils-core"
}
],
"settings": {
Expand Down
5 changes: 5 additions & 0 deletions packages/dgt-utils-core/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
registry=https://registry.npmjs.org/

@digita-ai:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
always-auth=true
45 changes: 45 additions & 0 deletions packages/dgt-utils-core/lib/asserts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

import { TypeCheck, isNull, isUndefined, isBoolean, isNumber, isBigInt, isString, isSymbol, isFunction, assert, isDefined, isObject } from './asserts';

const definedChecks: [ string, TypeCheck<any>, unknown[] ][] = [
[ 'isBoolean', isBoolean, [ true, false ] ],
[ 'isNumber', isNumber, [ 1 ] ],
[ 'isBigInt', isBigInt, [ BigInt(Number.MAX_SAFE_INTEGER) ] ],
[ 'isString', isString, [ 'this is a string' ] ],
[ 'isSymbol', isSymbol, [ Symbol('this is a symbol') ] ],
[ 'isFunction', isFunction, [ () => ({}) ] ],
[ 'isObject', isObject, [ {}, { 'some': 'field' }, { 1: 'other' }, { [Symbol('symbol')]: 'fields' } ] ],
];

const definedValues = definedChecks.map(([ name, check, goodValues ]) => goodValues).flat();

const undefinedChecks: [ string, TypeCheck<any>, unknown[] ][] = [
[ 'isNull', isNull, [ null ] ],
[ 'isUndefined', isUndefined, [ undefined ] ],
];

const undefinedValues = undefinedChecks.map(([ name, check, goodValues ]) => goodValues).flat();

const allChecks = definedChecks.concat(undefinedChecks).concat([ [ 'isDefined', isDefined, definedValues ] ]);

const allValues = definedValues.concat(undefinedValues);

const exampleFilter = (goodValues: unknown[]) => allValues.filter((value) => !goodValues.includes(value));

const goodMessage = 'should return true if the passed value is %p.';
const badMessage = 'should return false if the passed value is %p.';

describe('assert', () => {

it('should complete if the condition is true', () => expect(() => assert(true)).toBeDefined());
it('should throw an error if the condition is false', () => expect(() => assert(false)).toThrowError('Assertion failed.'));
it('should throw a specific error if a message is specified', () => expect(() => assert(false, 'msg')).toThrowError('msg'));

});

describe.each(allChecks)('%s', (name, check: TypeCheck<any>, goodValues: unknown[]) => {

it.each(goodValues)(goodMessage, (value) => expect(check(value)).toBe(true));
it.each(exampleFilter(goodValues))(badMessage, (value) => expect(check(value)).toBe(false));

});
99 changes: 99 additions & 0 deletions packages/dgt-utils-core/lib/asserts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@

/**
* Asserts that the given condition is true. If it is not, an error is thrown.
* The assertion function return type makes sure TypeScript takes the condition into account for type inference.
*
* Example usage:
*
* ```typescript
* assert(true); // always returns
* assert(false); // always throws
*
* const a = 'string';
* const b: unknown = someFunction();
* assert(a === b); // only returns if a === b
* console.log(b.length); // typescript knows that b is a string
*
* const c: unknown = someFunction();
* assert(isNumber(c)); // only returns if c is a number
* console.log(1 + c); // typescript knows that c is a number
* ```
*
* @param condition the condition to assert
* @param message an optional message to display if the condition is false
*/

export const assert: (
condition: boolean,
message?: string,
) => asserts condition = (
condition: boolean,
message?: string,
): asserts condition => {

if (!condition) throw new Error(message ?? `Assertion failed.`);

};

/**
* Shorthand for a type assertion function for type `<T>`.
*/
export type TypeCheck<T> = (value: unknown) => value is T;

/**
* Shorthand type assertion function for `value === null`.
*/
export const isNull: TypeCheck<null> = (value: unknown): value is null => value === null;

/**
* Shorthand type assertion function for `value === undefined`.
*/
export const isUndefined: TypeCheck<undefined> = (value: unknown): value is undefined => value === undefined;

/**
* Shorthand type assertion function for `value !== null && value !== null`.
*/
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions -- does not work with type parameter
export function isDefined <V> (value: V): value is Exclude<V, null | undefined> {

return value !== null && value !== undefined;

}

/**
* Shorthand type assertion function for `typeof value === 'boolean'`.
*/
export const isBoolean: TypeCheck<boolean> = (value: unknown): value is boolean => typeof value === 'boolean';

/**
* Shorthand type assertion function for `typeof value === 'number'`.
*/
export const isNumber: TypeCheck<number> = (value: unknown): value is number => typeof value === 'number';

/**
* Shorthand type assertion function for `typeof value === 'bigint'`.
*/
export const isBigInt: TypeCheck<bigint> = (value: unknown): value is bigint => typeof value === 'bigint';

/**
* Shorthand type assertion function for `typeof value === 'string'`.
*/
export const isString: TypeCheck<string> = (value: unknown): value is string => typeof value === 'string';

/**
* Shorthand type assertion function for `typeof value === 'symbol'`.
*/
export const isSymbol: TypeCheck<symbol> = (value: unknown): value is symbol => typeof value === 'symbol';

/**
* Shorthand type assertion function for `typeof value === 'function'`.
*/
// eslint-disable-next-line @typescript-eslint/ban-types -- basic type check
export const isFunction: TypeCheck<Function> = (value: unknown): value is Function => typeof value === 'function';

/**
* Shorthand type assertion function for `typeof value === object && value !== null`.
*/
export const isObject: TypeCheck<Record<string | number | symbol, unknown>> =
(value: unknown): value is Record<string | number | symbol, unknown> => typeof value === 'object' && value !== null;

2 changes: 2 additions & 0 deletions packages/dgt-utils-core/lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export * from './asserts';
Loading

0 comments on commit d54cb75

Please sign in to comment.