is/source/index.ts
Aaron Friel 7d37a7dbbc Fix unnecessary global inclusion of dom lib
TypeScript packages depending on this and importing `@sindresorhus/is`
end up with the lib.dom.d.ts in global scope thanks to the deleted
lines.

That's almost never what any user of the package would want, and it has
the side effect of putting a slew of common variable names (including
the variable names "event" and "name" and "parent") into a global scope.

It's particularly sinister because on many of these, TypeScript won't
complain, for example "name" is of type never.
2019-08-25 01:11:48 -07:00

387 lines
14 KiB
TypeScript

// TODO: Use the `URL` global when targeting Node.js 10
const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL;
export type Class<T = unknown> = new (...args: any[]) => T;
export const enum TypeName {
null = 'null',
boolean = 'boolean',
undefined = 'undefined',
string = 'string',
number = 'number',
bigint = 'bigint',
symbol = 'symbol',
Function = 'Function',
Generator = 'Generator',
GeneratorFunction = 'GeneratorFunction',
AsyncFunction = 'AsyncFunction',
Observable = 'Observable',
Array = 'Array',
Buffer = 'Buffer',
Object = 'Object',
RegExp = 'RegExp',
Date = 'Date',
Error = 'Error',
Map = 'Map',
Set = 'Set',
WeakMap = 'WeakMap',
WeakSet = 'WeakSet',
Int8Array = 'Int8Array',
Uint8Array = 'Uint8Array',
Uint8ClampedArray = 'Uint8ClampedArray',
Int16Array = 'Int16Array',
Uint16Array = 'Uint16Array',
Int32Array = 'Int32Array',
Uint32Array = 'Uint32Array',
Float32Array = 'Float32Array',
Float64Array = 'Float64Array',
BigInt64Array = 'BigInt64Array',
BigUint64Array = 'BigUint64Array',
ArrayBuffer = 'ArrayBuffer',
SharedArrayBuffer = 'SharedArrayBuffer',
DataView = 'DataView',
Promise = 'Promise',
URL = 'URL'
}
const {toString} = Object.prototype;
const isOfType = <T>(type: string) => (value: unknown): value is T => typeof value === type;
const getObjectType = (value: unknown): TypeName | undefined => {
const objectName = toString.call(value).slice(8, -1);
if (objectName) {
return objectName as TypeName;
}
return undefined;
};
const isObjectOfType = <T>(type: TypeName) => (value: unknown): value is T => getObjectType(value) === type;
function is(value: unknown): TypeName {
switch (value) {
case null:
return TypeName.null;
case true:
case false:
return TypeName.boolean;
default:
}
switch (typeof value) {
case 'undefined':
return TypeName.undefined;
case 'string':
return TypeName.string;
case 'number':
return TypeName.number;
case 'bigint':
return TypeName.bigint;
case 'symbol':
return TypeName.symbol;
default:
}
if (is.function_(value)) {
return TypeName.Function;
}
if (is.observable(value)) {
return TypeName.Observable;
}
if (is.array(value)) {
return TypeName.Array;
}
if (is.buffer(value)) {
return TypeName.Buffer;
}
const tagType = getObjectType(value);
if (tagType) {
return tagType;
}
if (value instanceof String || value instanceof Boolean || value instanceof Number) {
throw new TypeError('Please don\'t use object wrappers for primitive types');
}
return TypeName.Object;
}
is.undefined = isOfType<undefined>('undefined');
is.string = isOfType<string>('string');
const isNumberType = isOfType<number>('number');
is.number = (value: unknown): value is number => isNumberType(value) && !is.nan(value);
is.bigint = isOfType<bigint>('bigint');
// eslint-disable-next-line @typescript-eslint/ban-types
is.function_ = isOfType<Function>('function');
is.null_ = (value: unknown): value is null => value === null;
is.class_ = (value: unknown): value is Class => is.function_(value) && value.toString().startsWith('class ');
is.boolean = (value: unknown): value is boolean => value === true || value === false;
is.symbol = isOfType<symbol>('symbol');
is.numericString = (value: unknown): value is string =>
is.string(value) && value.length > 0 && !Number.isNaN(Number(value));
is.array = Array.isArray;
is.buffer = (value: unknown): value is Buffer => !is.nullOrUndefined(value) && !is.nullOrUndefined((value as Buffer).constructor) && is.function_((value as Buffer).constructor.isBuffer) && (value as Buffer).constructor.isBuffer(value);
is.nullOrUndefined = (value: unknown): value is null | undefined => is.null_(value) || is.undefined(value);
is.object = (value: unknown): value is object => !is.null_(value) && (typeof value === 'object' || is.function_(value));
is.iterable = (value: unknown): value is IterableIterator<unknown> => !is.nullOrUndefined(value) && is.function_((value as IterableIterator<unknown>)[Symbol.iterator]);
is.asyncIterable = (value: unknown): value is AsyncIterableIterator<unknown> => !is.nullOrUndefined(value) && is.function_((value as AsyncIterableIterator<unknown>)[Symbol.asyncIterator]);
is.generator = (value: unknown): value is Generator => is.iterable(value) && is.function_(value.next) && is.function_(value.throw);
is.nativePromise = (value: unknown): value is Promise<unknown> =>
isObjectOfType<Promise<unknown>>(TypeName.Promise)(value);
const hasPromiseAPI = (value: unknown): value is Promise<unknown> =>
is.object(value) &&
is.function_((value as Promise<unknown>).then) && // eslint-disable-line promise/prefer-await-to-then
is.function_((value as Promise<unknown>).catch);
is.promise = (value: unknown): value is Promise<unknown> => is.nativePromise(value) || hasPromiseAPI(value);
is.generatorFunction = isObjectOfType<GeneratorFunction>(TypeName.GeneratorFunction);
// eslint-disable-next-line @typescript-eslint/ban-types
is.asyncFunction = isObjectOfType<Function>(TypeName.AsyncFunction);
// eslint-disable-next-line no-prototype-builtins, @typescript-eslint/ban-types
is.boundFunction = (value: unknown): value is Function => is.function_(value) && !value.hasOwnProperty('prototype');
is.regExp = isObjectOfType<RegExp>(TypeName.RegExp);
is.date = isObjectOfType<Date>(TypeName.Date);
is.error = isObjectOfType<Error>(TypeName.Error);
is.map = (value: unknown): value is Map<unknown, unknown> => isObjectOfType<Map<unknown, unknown>>(TypeName.Map)(value);
is.set = (value: unknown): value is Set<unknown> => isObjectOfType<Set<unknown>>(TypeName.Set)(value);
is.weakMap = (value: unknown): value is WeakMap<object, unknown> => isObjectOfType<WeakMap<object, unknown>>(TypeName.WeakMap)(value);
is.weakSet = (value: unknown): value is WeakSet<object> => isObjectOfType<WeakSet<object>>(TypeName.WeakSet)(value);
is.int8Array = isObjectOfType<Int8Array>(TypeName.Int8Array);
is.uint8Array = isObjectOfType<Uint8Array>(TypeName.Uint8Array);
is.uint8ClampedArray = isObjectOfType<Uint8ClampedArray>(TypeName.Uint8ClampedArray);
is.int16Array = isObjectOfType<Int16Array>(TypeName.Int16Array);
is.uint16Array = isObjectOfType<Uint16Array>(TypeName.Uint16Array);
is.int32Array = isObjectOfType<Int32Array>(TypeName.Int32Array);
is.uint32Array = isObjectOfType<Uint32Array>(TypeName.Uint32Array);
is.float32Array = isObjectOfType<Float32Array>(TypeName.Float32Array);
is.float64Array = isObjectOfType<Float64Array>(TypeName.Float64Array);
is.bigInt64Array = isObjectOfType<BigInt64Array>(TypeName.BigInt64Array);
is.bigUint64Array = isObjectOfType<BigUint64Array>(TypeName.BigUint64Array);
is.arrayBuffer = isObjectOfType<ArrayBuffer>(TypeName.ArrayBuffer);
is.sharedArrayBuffer = isObjectOfType<SharedArrayBuffer>(TypeName.SharedArrayBuffer);
is.dataView = isObjectOfType<DataView>(TypeName.DataView);
is.directInstanceOf = <T>(instance: unknown, class_: Class<T>): instance is T => Object.getPrototypeOf(instance) === class_.prototype;
is.urlInstance = (value: unknown): value is URL => isObjectOfType<URL>(TypeName.URL)(value);
is.urlString = (value: unknown): value is string => {
if (!is.string(value)) {
return false;
}
try {
new URLGlobal(value); // eslint-disable-line no-new
return true;
} catch {
return false;
}
};
// TODO: Use the `not` operator with a type guard here when it's available.
// Example: `is.truthy = (value: unknown): value is (not false | not 0 | not '' | not undefined | not null) => Boolean(value);`
is.truthy = (value: unknown) => Boolean(value);
// Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);`
is.falsy = (value: unknown) => !value;
is.nan = (value: unknown) => Number.isNaN(value as number);
const primitiveTypeOfTypes = new Set([
'undefined',
'string',
'number',
'bigint',
'boolean',
'symbol'
]);
// TODO: This should be able to be `not object` when the `not` operator is out
export type Primitive = null | undefined | string | number | bigint | boolean | symbol;
is.primitive = (value: unknown): value is Primitive => is.null_(value) || primitiveTypeOfTypes.has(typeof value);
is.integer = (value: unknown): value is number => Number.isInteger(value as number);
is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number);
is.plainObject = (value: unknown): value is {[key: string]: unknown} => {
// From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js
if (getObjectType(value) !== TypeName.Object) {
return false;
}
const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.getPrototypeOf({});
};
const typedArrayTypes = new Set([
TypeName.Int8Array,
TypeName.Uint8Array,
TypeName.Uint8ClampedArray,
TypeName.Int16Array,
TypeName.Uint16Array,
TypeName.Int32Array,
TypeName.Uint32Array,
TypeName.Float32Array,
TypeName.Float64Array,
TypeName.BigInt64Array,
TypeName.BigUint64Array
]);
export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
is.typedArray = (value: unknown): value is TypedArray => {
const objectType = getObjectType(value);
if (objectType === undefined) {
return false;
}
return typedArrayTypes.has(objectType);
};
export interface ArrayLike<T> {
readonly length: number;
readonly [index: number]: T;
}
const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0;
is.arrayLike = (value: unknown): value is ArrayLike<unknown> => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike<unknown>).length);
is.inRange = (value: number, range: number | number[]): value is number => {
if (is.number(range)) {
return value >= Math.min(0, range) && value <= Math.max(range, 0);
}
if (is.array(range) && range.length === 2) {
return value >= Math.min(...range) && value <= Math.max(...range);
}
throw new TypeError(`Invalid range: ${JSON.stringify(range)}`);
};
const NODE_TYPE_ELEMENT = 1;
const DOM_PROPERTIES_TO_CHECK = [
'innerHTML',
'ownerDocument',
'style',
'attributes',
'nodeValue'
];
is.domElement = (value: unknown): value is Element => is.object(value) && (value as Element).nodeType === NODE_TYPE_ELEMENT && is.string((value as Element).nodeName) &&
!is.plainObject(value) && DOM_PROPERTIES_TO_CHECK.every(property => property in (value as Element));
export interface ObservableLike {
subscribe(observer: (value: unknown) => void): void;
[Symbol.observable](): ObservableLike;
}
is.observable = (value: unknown): value is ObservableLike => {
if (!value) {
return false;
}
// eslint-disable-next-line no-use-extend-native/no-use-extend-native
if ((value as any)[Symbol.observable] && value === (value as any)[Symbol.observable]()) {
return true;
}
if ((value as any)['@@observable'] && value === (value as any)['@@observable']()) {
return true;
}
return false;
};
// eslint-disable-next-line @typescript-eslint/ban-types
export type NodeStream = object & {readonly pipe: Function};
is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is.function_((value as NodeStream).pipe) && !is.observable(value);
is.infinite = (value: unknown): value is number => value === Infinity || value === -Infinity;
const isAbsoluteMod2 = (remainder: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === remainder;
is.evenInteger = isAbsoluteMod2(0);
is.oddInteger = isAbsoluteMod2(1);
is.emptyArray = (value: unknown): value is never[] => is.array(value) && value.length === 0;
is.nonEmptyArray = (value: unknown): value is unknown[] => is.array(value) && value.length > 0;
is.emptyString = (value: unknown): value is '' => is.string(value) && value.length === 0;
// TODO: Use `not ''` when the `not` operator is available.
is.nonEmptyString = (value: unknown): value is string => is.string(value) && value.length > 0;
const isWhiteSpaceString = (value: unknown): value is string => is.string(value) && /\S/.test(value) === false;
is.emptyStringOrWhitespace = (value: unknown): value is string => is.emptyString(value) || isWhiteSpaceString(value);
is.emptyObject = (value: unknown): value is {[key: string]: never} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length === 0;
// TODO: Use `not` operator here to remove `Map` and `Set` from type guard:
// - https://github.com/Microsoft/TypeScript/pull/29317
is.nonEmptyObject = (value: unknown): value is {[key: string]: unknown} => is.object(value) && !is.map(value) && !is.set(value) && Object.keys(value).length > 0;
is.emptySet = (value: unknown): value is Set<never> => is.set(value) && value.size === 0;
is.nonEmptySet = (value: unknown): value is Set<unknown> => is.set(value) && value.size > 0;
is.emptyMap = (value: unknown): value is Map<never, never> => is.map(value) && value.size === 0;
is.nonEmptyMap = (value: unknown): value is Map<unknown, unknown> => is.map(value) && value.size > 0;
export type Predicate = (value: unknown) => boolean;
type ArrayMethod = (fn: (value: unknown, index: number, array: unknown[]) => boolean, thisArg?: unknown) => boolean;
const predicateOnArray = (method: ArrayMethod, predicate: Predicate, values: unknown[]) => {
if (is.function_(predicate) === false) {
throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`);
}
if (values.length === 0) {
throw new TypeError('Invalid number of values');
}
return method.call(values, predicate);
};
is.any = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.some, predicate, values);
is.all = (predicate: Predicate, ...values: unknown[]): boolean => predicateOnArray(Array.prototype.every, predicate, values);
// Some few keywords are reserved, but we'll populate them for Node.js users
// See https://github.com/Microsoft/TypeScript/issues/2536
Object.defineProperties(is, {
class: {
value: is.class_
},
function: {
value: is.function_
},
null: {
value: is.null_
}
});
export default is;
// For CommonJS default export support
module.exports = is;
module.exports.default = is;