From fb41c2ce6976436270a7946a22c37f1f63eeb783 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Sun, 15 Sep 2019 11:40:30 +0700 Subject: [PATCH] Add @rondo.dev/common guard fn This is to help guard against types that can be undefined since the recommended eslint rules recommend against using `!` operator on values like: const obj: Obj | undefined = ... obj!.value Now we can use: const obj: Obj | undefined = ... const obj2: Obj = guard(isDefined, obj) obj.value The guard function will throw an error if obj parameter is undefined. --- packages/common/src/guard.test.ts | 60 +++++++++++++++++++++++++++++++ packages/common/src/guard.ts | 35 ++++++++++++++++++ packages/common/src/index.ts | 1 + 3 files changed, 96 insertions(+) create mode 100644 packages/common/src/guard.test.ts create mode 100644 packages/common/src/guard.ts diff --git a/packages/common/src/guard.test.ts b/packages/common/src/guard.test.ts new file mode 100644 index 0000000..a208961 --- /dev/null +++ b/packages/common/src/guard.test.ts @@ -0,0 +1,60 @@ +import {guard, isNumber, isString, isDefined, isUndefined, isNull} from './guard' + +function getNumber(n: number | null): number | null { + return n +} + +function getString(str: string | undefined | null): string | undefined | null { + return str +} + +describe('guard', () => { + + describe('converts T | undefined | null to T', () => { + describe('isDefined', () => { + it('converts T | undefined | null to T', () => { + const s1: string | undefined | null = getString('test') + const s2: string = guard(isDefined, s1) + expect(s2).toBe('test') + }) + }) + + describe('isString', () => { + it('converts string | undefined | null to string', () => { + const s1: string | undefined | null = getString('test') + const s2: string = guard(isString, s1) + expect(s2).toBe('test') + }) + }) + + describe('isNumber', () => { + it('converts number | undefined | null to number', () => { + const n1: number | undefined | null = getNumber(1) + const n2: number = guard(isNumber, n1) + expect(n2).toBe(1) + }) + }) + + describe('isUndefined', () => { + const s1: string | undefined | null = getString(undefined) + const s2: undefined = guard(isUndefined, s1) + expect(s2).toBe(undefined) + }) + + describe('isNull', () => { + const s1: string | undefined | null = getString(null) + const s2: null = guard(isNull, s1) + expect(s2).toBe(null) + }) + + }) + + describe('errors', () => { + it('guards against unexpected types', () => { + const s1: string | undefined | null = getString(null) + expect(() => guard(isString, s1)) + .toThrowError('Value null does not satisfy constraint: isString') + }) + }) + +}) diff --git a/packages/common/src/guard.ts b/packages/common/src/guard.ts new file mode 100644 index 0000000..f6b6956 --- /dev/null +++ b/packages/common/src/guard.ts @@ -0,0 +1,35 @@ +type CheckType = (t: unknown) => t is T + +export function guard( + checkType: CheckType, + t: unknown, +): Expected { + if (!checkType(t)) { + throw new Error( + `Value ${t} does not satisfy constraint: ${checkType.name}`) + } + + return t +} + +export function isUndefined(t: unknown): t is undefined { + return t === undefined +} + +export function isNull(t: unknown): t is null { + return t === null +} + +export function isDefined( + t: unknown, +): t is T { + return t !== undefined && t !== null +} + +export function isNumber(t: unknown): t is number { + return typeof t === 'number' +} + +export function isString(t: unknown): t is string { + return typeof t === 'string' +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d1ce5c8..d7a101b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -6,6 +6,7 @@ export * from './ILogger' export * from './IRole' export * from './StringUtils' export * from './filterProps' +export * from './guard' export * from './indexBy' export * from './types' export * from './without'