From 9a1c1ebd807f0c16e3a2924d07a0b8a7a9af5af1 Mon Sep 17 00:00:00 2001 From: Craig Weber Date: Tue, 22 Jun 2021 12:31:00 -0400 Subject: [PATCH] feat: Make isEmpty a type guard (#16) --- src/index.ts | 2 ++ src/utils/is-arguments.ts | 2 +- src/utils/is-array-of-strings.ts | 2 +- src/utils/is-array.ts | 2 +- src/utils/is-boolean.ts | 3 +++ src/utils/is-empty.ts | 31 ++++++++++++++++++++++++++++--- src/utils/is-enum-value.ts | 2 +- src/utils/is-null.ts | 3 +++ src/utils/is-number.ts | 2 +- src/utils/is-object.ts | 2 +- src/utils/is-promise-like.ts | 2 +- src/utils/is-promise.ts | 2 +- src/utils/is-string.ts | 2 +- tests/utils/is-boolean.test.ts | 16 ++++++++++++++++ tests/utils/is-null.test.ts | 23 +++++++++++++++++++++++ 15 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 src/utils/is-boolean.ts create mode 100644 src/utils/is-null.ts create mode 100644 tests/utils/is-boolean.test.ts create mode 100644 tests/utils/is-null.test.ts diff --git a/src/index.ts b/src/index.ts index 1ec0d7c..a0ea26c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ export * from './utils/has-defined'; export * from './utils/is-arguments'; export * from './utils/is-array'; export * from './utils/is-array-of-strings'; +export * from './utils/is-boolean'; export * from './utils/is-empty'; export * from './utils/is-enum-value'; export * from './utils/is-map-with-values-of-type'; @@ -23,6 +24,7 @@ export * from './utils/is-promise-like'; export * from './utils/is-set'; export * from './utils/is-string'; export * from './utils/is-undefined'; +export * from './utils/is-null'; export * from './utils/chunk'; export * from './utils/flatten'; diff --git a/src/utils/is-arguments.ts b/src/utils/is-arguments.ts index 265e042..57ae7ad 100644 --- a/src/utils/is-arguments.ts +++ b/src/utils/is-arguments.ts @@ -6,6 +6,6 @@ import { getTagString } from './get-tag-string'; * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments * @returns `true` if `o` is a function's array-like `arguments` variable */ -export function isArguments(o: any): boolean { +export function isArguments(o: unknown): o is IArguments { return getTagString(o) === '[object Arguments]'; } diff --git a/src/utils/is-array-of-strings.ts b/src/utils/is-array-of-strings.ts index 70e65ae..7efeaa2 100644 --- a/src/utils/is-array-of-strings.ts +++ b/src/utils/is-array-of-strings.ts @@ -8,7 +8,7 @@ import { isString } from './is-string'; * not counted as `string` values, even if the TypeScript compiler's `strictNullChecks` * flag is set to `false` in your project. */ -export function isArrayOfStrings(values: any): values is string[] { +export function isArrayOfStrings(values: unknown): values is string[] { if (!isArray(values)) { return false; } diff --git a/src/utils/is-array.ts b/src/utils/is-array.ts index d4ac950..59f7749 100644 --- a/src/utils/is-array.ts +++ b/src/utils/is-array.ts @@ -3,6 +3,6 @@ * * @returns `true` if `o` is an `Array`, regardless of the types that it contains */ -export function isArray(o: any): o is unknown[] { +export function isArray(o: unknown): o is unknown[] { return Array.isArray(o); } diff --git a/src/utils/is-boolean.ts b/src/utils/is-boolean.ts new file mode 100644 index 0000000..4e67aa5 --- /dev/null +++ b/src/utils/is-boolean.ts @@ -0,0 +1,3 @@ +export function isBoolean(o: unknown): o is boolean { + return o === true || o === false; +} diff --git a/src/utils/is-empty.ts b/src/utils/is-empty.ts index 1c5ed36..4166454 100644 --- a/src/utils/is-empty.ts +++ b/src/utils/is-empty.ts @@ -2,7 +2,32 @@ import { isArray } from './is-array'; import { isString } from './is-string'; import { isArguments } from './is-arguments'; import { isUndefined } from './is-undefined'; +import { isNull } from './is-null'; +import { isBoolean } from './is-boolean'; +import { isNumber } from './is-number'; import { isSet } from './is-set'; +import { isObject } from './is-object'; + + +interface IEmptyArguments extends IArguments { + length: 0; +} + +interface IEmptyObj { + [s: string]: never; +} + +type IEmptyTypes = ( + null | + undefined | + boolean | + number | + never[] | + '' | + IEmptyArguments | + Set | + IEmptyObj +); /** * Checks if `o` is an empty object. An object is "empty" if it: @@ -13,8 +38,8 @@ import { isSet } from './is-set'; * * @returns `true` if `o` is empty */ -export function isEmpty(o: any): boolean { - if (o === null || isUndefined(o)) { +export function isEmpty(o: unknown): o is IEmptyTypes { + if (isNull(o) || isUndefined(o) || isBoolean(o) || isNumber(o)) { return true; } if (isArray(o) || isString(o) || isArguments(o)) { @@ -23,5 +48,5 @@ export function isEmpty(o: any): boolean { if (isSet(o)) { return o.size === 0; } - return Object.keys(o).length === 0; + return isObject(o) && Object.keys(o).length === 0; } diff --git a/src/utils/is-enum-value.ts b/src/utils/is-enum-value.ts index 0951c4e..12fa9c2 100644 --- a/src/utils/is-enum-value.ts +++ b/src/utils/is-enum-value.ts @@ -1,7 +1,7 @@ /** * Type guard to check to see if the given value is a valid value for the enum. */ -export function isEnumValue(enumType: T, value: any): value is T[keyof T] { +export function isEnumValue(enumType: T, value: unknown): value is T[keyof T] { return (Object.keys(enumType) as Array) .map((key) => { return enumType[key]; diff --git a/src/utils/is-null.ts b/src/utils/is-null.ts new file mode 100644 index 0000000..065e606 --- /dev/null +++ b/src/utils/is-null.ts @@ -0,0 +1,3 @@ +export function isNull(val: unknown): val is null { + return val === null; +} diff --git a/src/utils/is-number.ts b/src/utils/is-number.ts index 5a85103..9801fba 100644 --- a/src/utils/is-number.ts +++ b/src/utils/is-number.ts @@ -8,6 +8,6 @@ import { isObject } from './is-object'; * considered `number`s. If you need to check for one of those values, you can use the * built-in `Number.isNaN` or `Number.isFinite` functions. */ -export function isNumber(o: any): o is number { +export function isNumber(o: unknown): o is number { return typeof o === 'number' || (isObject(o) && getTagString(o) === '[object Number]'); } diff --git a/src/utils/is-object.ts b/src/utils/is-object.ts index 0592825..3e2ef27 100644 --- a/src/utils/is-object.ts +++ b/src/utils/is-object.ts @@ -9,7 +9,7 @@ * @see https://github.com/jashkenas/underscore/blob/d5fe0fd4060f13b40608cb9d92eda6d857e8752c/underscore.js#L1322 * @returns `true` if `o` is an `object` */ -export function isObject(o: any): o is object { +export function isObject(o: unknown): o is object { let type = typeof o; return o !== null && (type === 'object' || type === 'function'); diff --git a/src/utils/is-promise-like.ts b/src/utils/is-promise-like.ts index 5dca394..6b91543 100644 --- a/src/utils/is-promise-like.ts +++ b/src/utils/is-promise-like.ts @@ -8,6 +8,6 @@ import { isObject } from './is-object'; * * @returns `true` if `o` is `Promise`-like (i.e. has a `then` function) */ -export function isPromiseLike(o: any): o is PromiseLike { +export function isPromiseLike(o: unknown): o is PromiseLike { return isPromise(o) || (isObject(o) && typeof (o as any).then === 'function'); } diff --git a/src/utils/is-promise.ts b/src/utils/is-promise.ts index 3435381..8989925 100644 --- a/src/utils/is-promise.ts +++ b/src/utils/is-promise.ts @@ -6,6 +6,6 @@ import { getTagString } from './get-tag-string'; * * @returns `true` if `o` is a `Promise` */ -export function isPromise(o: any): o is Promise { +export function isPromise(o: unknown): o is Promise { return isObject(o) && getTagString(o) === '[object Promise]'; } diff --git a/src/utils/is-string.ts b/src/utils/is-string.ts index 411b3e0..617e1d0 100644 --- a/src/utils/is-string.ts +++ b/src/utils/is-string.ts @@ -8,6 +8,6 @@ import { getTagString } from './get-tag-string'; * `string` values, even if the TypeScript compiler's `strictNullChecks` flag is set to * `false` in your project. */ -export function isString(o: any): o is string { +export function isString(o: unknown): o is string { return typeof o === 'string' || (isObject(o) && getTagString(o) === '[object String]'); } diff --git a/tests/utils/is-boolean.test.ts b/tests/utils/is-boolean.test.ts new file mode 100644 index 0000000..fa5f58e --- /dev/null +++ b/tests/utils/is-boolean.test.ts @@ -0,0 +1,16 @@ +import { expect } from 'chai'; +import * as t from '../../src/index'; + + +describe('isBoolean', () => { + + it('correctly classifies bools', () => { + expect(t.isBoolean(true)).to.strictlyEqual(true); + expect(t.isBoolean(false)).to.strictlyEqual(true); + expect(t.isBoolean(null)).to.strictlyEqual(false); + expect(t.isBoolean(0)).to.strictlyEqual(false); + expect(t.isBoolean('')).to.strictlyEqual(false); + expect(t.isBoolean({})).to.strictlyEqual(false); + }); + +}); diff --git a/tests/utils/is-null.test.ts b/tests/utils/is-null.test.ts new file mode 100644 index 0000000..57df2fc --- /dev/null +++ b/tests/utils/is-null.test.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import * as t from '../../src/index'; + + +describe('isNull', () => { + + it('correctly classifies null things', () => { + let o; + + expect(t.isNull(o)).to.strictlyEqual(false); + expect(t.isNull(undefined)).to.strictlyEqual(false); + expect(t.isNull(null)).to.strictlyEqual(true); + + let obj: any = { bar: 1, baz: false, bag: undefined, bah: null }; + + expect(t.isNull(obj.foo)).to.strictlyEqual(false); + expect(t.isNull(obj.bar)).to.strictlyEqual(false); + expect(t.isNull(obj.baz)).to.strictlyEqual(false); + expect(t.isNull(obj.bag)).to.strictlyEqual(false); + expect(t.isNull(obj.bah)).to.strictlyEqual(true); + }); + +});