1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2024 Per Cederberg. All rights reserved.
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the BSD license.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * See the RapidContext LICENSE for more details.
13 */
14
15/**
16 * Provides predicate functions for type checking and equality.
17 * @namespace RapidContext.Fn
18 */
19
20/**
21 * Checks if a value is either `null` or `undefined`.
22 *
23 * @param {*} val the value to check
24 * @return {boolean} `true` on match, or `false` otherwise
25 * @name isNil
26 * @memberof RapidContext.Fn
27 */
28export function isNil(val) {
29 return val === null || val === undefined;
30}
31
32/**
33 * Checks if a value is `null` (but not `undefined`).
34 *
35 * @param {*} val the value to check
36 * @return {boolean} `true` on match, or `false` otherwise
37 * @name isNull
38 * @memberof RapidContext.Fn
39 */
40export function isNull(val) {
41 return val === null;
42}
43
44/**
45 * Checks if `typeof(val)` matches a string or an array element. Returns a
46 * bound function if only the first argument is specified.
47 *
48 * @param {string|Array} type the type name or array of type names
49 * @param {*} [val] the value to check
50 * @return {boolean|function} the match result, or a bound function
51 * @name isTypeOf
52 * @memberof RapidContext.Fn
53 */
54export function isTypeOf(type, val) {
55 if (arguments.length < 2) {
56 return isTypeOf.bind(null, ...arguments);
57 } else {
58 return Array.isArray(type) ? type.includes(typeof(val)) : typeof(val) === type;
59 }
60}
61
62/**
63 * Checks if a value is `undefined` (but not `null`).
64 *
65 * @param {*} val the value to check
66 * @return {boolean} `true` on match, or `false` otherwise
67 * @name isUndefined
68 * @memberof RapidContext.Fn
69 */
70export function isUndefined(val) {
71 return isTypeOf('undefined', val);
72}
73
74/**
75 * Checks if a value is a `boolean`` (i.e. `typeof(..) = "boolean"`).
76 *
77 * @param {*} val the value to check
78 * @return {boolean} `true` on match, or `false` otherwise
79 * @name isBoolean
80 * @memberof RapidContext.Fn
81 */
82export function isBoolean(val) {
83 return isTypeOf('boolean', val);
84}
85
86/**
87 * Checks if a value is a `function` (i.e. `typeof(..) = "function"`).
88 *
89 * @param {*} val the value to check
90 * @return {boolean} `true` on match, or `false` otherwise
91 * @name isFunction
92 * @memberof RapidContext.Fn
93 */
94export function isFunction(val) {
95 return isTypeOf('function', val);
96}
97
98/**
99 * Checks if a value is a `number` (i.e. `typeof(..) = "number"`).
100 *
101 * @param {*} val the value to check
102 * @return {boolean} `true` on match, or `false` otherwise
103 * @name isNumber
104 * @memberof RapidContext.Fn
105 */
106export function isNumber(val) {
107 return isTypeOf('number', val);
108}
109
110/**
111 * Checks if a value is a `bigint` (i.e. `typeof(..) = "bigint"`).
112 *
113 * @param {*} val the value to check
114 * @return {boolean} `true` on match, or `false` otherwise
115 * @name isBigInt
116 * @memberof RapidContext.Fn
117 */
118export function isBigInt(val) {
119 return isTypeOf('bigint', val);
120}
121
122/**
123 * Checks if a value is a `string` (i.e. `typeof(..) = "string"`).
124 *
125 * @param {*} val the value to check
126 * @return {boolean} `true` on match, or `false` otherwise
127 * @name isString
128 * @memberof RapidContext.Fn
129 */
130export function isString(val) {
131 return isTypeOf('string', val);
132}
133
134/**
135 * Checks if a value is a plain `Object` (and not based on another prototype).
136 *
137 * @param {*} val the value to check
138 * @return {boolean} `true` on match, or `false` otherwise
139 * @name isObject
140 * @memberof RapidContext.Fn
141 */
142export function isObject(val) {
143 // Note: Some built-in objects (e.g. Arguments) share prototype with Object
144 // Note: Object.prototype.toString() gives weird result in some other cases
145 let proto = Object.prototype;
146 let str = proto.toString;
147 return !!val && Object.getPrototypeOf(val) === proto && str.call(val) === '[object Object]';
148}
149
150/**
151 * Checks if a value is `instanceof` a constructor function. Returns a bound
152 * function if only the first argument is specified.
153 *
154 * @param {function} constructor the constructor function or class
155 * @param {*} val the value to check
156 * @return {boolean|function} the match result, or a bound function
157 * @name isInstanceOf
158 * @memberof RapidContext.Fn
159 */
160export function isInstanceOf(constructor, val) {
161 if (arguments.length < 2) {
162 return isInstanceOf.bind(null, ...arguments);
163 } else {
164 return val instanceof constructor;
165 }
166}
167
168/**
169 * Checks if a value is an instance of `Error`.
170 *
171 * @param {*} val the value to check
172 * @return {boolean} `true` on match, or `false` otherwise
173 * @name isError
174 * @memberof RapidContext.Fn
175 */
176export function isError(val) {
177 return isInstanceOf(Error, val);
178}
179
180/**
181 * Checks if a value is an instance of `Date`.
182 *
183 * @param {*} val the value to check
184 * @return {boolean} `true` on match, or `false` otherwise
185 * @name isDate
186 * @memberof RapidContext.Fn
187 */
188export function isDate(val) {
189 return isInstanceOf(Date, val);
190}
191
192/**
193 * Checks if a value is an instance of `RegExp`.
194 *
195 * @param {*} val the value to check
196 * @return {boolean} `true` on match, or `false` otherwise
197 * @name isRegExp
198 * @memberof RapidContext.Fn
199 */
200export function isRegExp(val) {
201 return isInstanceOf(RegExp, val);
202}
203
204/**
205 * Checks if a value is an instance of `Promise`.
206 *
207 * @param {*} val the value to check
208 * @return {boolean} `true` on match, or `false` otherwise
209 * @name isPromise
210 * @memberof RapidContext.Fn
211 */
212export function isPromise(val) {
213 return isInstanceOf(Promise, val);
214}
215
216const isArray = Array.isArray;
217
218/**
219 * Checks if a value is Array-like. This is an object with a numeric `length`
220 * property (but not a function).
221 *
222 * @param {*} val the value to check
223 * @return {boolean} `true` on match, or `false` otherwise
224 * @name isArrayLike
225 * @memberof RapidContext.Fn
226 */
227export function isArrayLike(val) {
228 return !!val && isNumber(val.length) && val.length >= 0 && !isFunction(val);
229}
230
231/**
232 * Checks if a value is Set-like. This is an object with a numeric `size`
233 * property, and `has()` and `keys()` methods.
234 *
235 * @param {*} val the value to check
236 * @return {boolean} `true` on match, or `false` otherwise
237 * @name isSetLike
238 * @memberof RapidContext.Fn
239 */
240export function isSetLike(val) {
241 return !!val && isNumber(val.size) && isFunction(val.has) && isFunction(val.keys);
242}
243
244/**
245 * Checks if a value is iterable (i.e. follows the iterable protocol). This is
246 * an object with a `Symbol.iterator` method returning the values.
247 *
248 * @param {*} val the value to check
249 * @return {boolean} `true` on match, or `false` otherwise
250 * @name isIterable
251 * @memberof RapidContext.Fn
252 */
253export function isIterable(val) {
254 return !!val && isFunction(val[Symbol.iterator]);
255}
256
257/**
258 * Checks if an object has an own property with a specified key. Returns a
259 * bound function if only the first argument is specified.
260 *
261 * @param {string} key the property key to check for
262 * @param {Object} [obj] the object to check
263 * @return {boolean|function} the match result, or a bound function
264 * @name hasProperty
265 * @memberof RapidContext.Fn
266 */
267export function hasProperty(key, obj) {
268 if (arguments.length < 2) {
269 return hasProperty.bind(null, ...arguments);
270 } else {
271 return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
272 }
273}
274
275/**
276 * Checks if a value is set. Returns `false` for `null`, `undefined`, empty
277 * arrays, empty objects or empty strings. Boolean `false` and `0` will return
278 * `true`, as these are defined values.
279 *
280 * @param {*} val the value to check
281 * @return {boolean} `true` if the value is set, or `false` otherwise
282 * @name hasValue
283 * @memberof RapidContext.Fn
284 */
285export function hasValue(val) {
286 if (isNumber(val)) {
287 return !isNaN(val);
288 } else if (isArrayLike(val)) {
289 return val.length > 0;
290 } else if (isObject(val)) {
291 return Object.keys(val).length > 0;
292 } else if (isSetLike(val)) {
293 return val.size > 0;
294 } else {
295 return isBoolean(val) || isBigInt(val) || !!val;
296 }
297}
298
299/**
300 * Checks if one or more values are equal. Supports recursively comparing both
301 * arrays and objects, as well as standard values. Returns a bound function if
302 * only the first argument is specified. Also returns a bound function if one
303 * argument is a function.
304 *
305 * @param {...*} values the values to compare
306 * @return {boolean|function} the test result, or a bound function
307 * @name eq
308 * @memberof RapidContext.Fn
309 *
310 * @example
311 * eq(1, 1, 1, 3) //==> false
312 * eq({ a: 1, b: 2 }, { b: 2, a: 1 }) //==> true
313 *
314 * @example
315 * let isAbc = eq("abc");
316 * isAbc("abc") //==> true
317 *
318 * @example
319 * let hasValidA = eq(42, get("a"));
320 * hasValidA({ a: 42 }) //==> true
321 */
322export function eq(...values) {
323 function test() {
324 let valid = [];
325 for (let el of values) {
326 if (isFunction(el)) {
327 try {
328 el = el.apply(null, arguments);
329 } catch (ignore) {
330 return false;
331 }
332 }
333 if (valid.length > 0 && !isEq(valid[0], el)) {
334 return false;
335 }
336 valid.push(el);
337 }
338 return true;
339 }
340 function isEq(a, b) {
341 if (isArray(a) || isArray(b)) {
342 const aLen = isArray(a) && a.length;
343 const bLen = isArray(b) && b.length;
344 return aLen === bLen && a.every((el, i) => isEq(el, b[i]));
345 } else if (isObject(a) || isObject(b)) {
346 const aKeys = isObject(a) && Object.keys(a).sort();
347 const bKeys = isObject(b) && Object.keys(b).sort();
348 return isEq(aKeys, bKeys) && aKeys.every((k) => isEq(a[k], b[k]));
349 } else {
350 return a === b;
351 }
352 }
353 if (arguments.length < 2) {
354 return eq.bind(null, ...arguments);
355 } else if (values.some(isFunction)) {
356 return test;
357 } else {
358 return test();
359 }
360}
361
362export default {
363 isNil, isNull, isUndefined, isBoolean, isFunction, isNumber, isBigInt,
364 isString, isObject, isError, isDate, isRegExp, isPromise, isArrayLike,
365 isSetLike, isIterable, isTypeOf, isInstanceOf, hasProperty, hasValue, eq
366};
367
RapidContext
Access · Discovery · Insight
www.rapidcontext.com