feat(isObjectLike, isArguments): Add isObjectLike and isArguments with compatibility test (#279)

* Add isObjectLike

* Add isArguments

* remove duplicate function noop

* Remove un duplicated function

* add bench

* update compatibility

* Add docs

---------

Co-authored-by: Sojin Park <raon0211@toss.im>
This commit is contained in:
Dayong Lee 2024-07-25 16:36:57 +09:00 committed by GitHub
parent 834a71589a
commit a02b4158eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 452 additions and 9 deletions

View File

@ -0,0 +1,25 @@
import { bench, describe } from 'vitest';
import { isArguments as isArgumentsToolkit } from 'es-toolkit';
import { isArguments as isArgumentsLodash } from 'lodash';
describe('isArguments', () => {
bench('es-toolkit/isArguments', () => {
isArgumentsToolkit([1, 2, 3]);
isArgumentsToolkit(true);
isArgumentsToolkit(new Date());
isArgumentsToolkit(new Error());
isArgumentsToolkit(1);
isArgumentsToolkit(/x/);
isArgumentsToolkit('a');
});
bench('lodash/isArguments', () => {
isArgumentsLodash([1, 2, 3]);
isArgumentsLodash(true);
isArgumentsLodash(new Date());
isArgumentsLodash(new Error());
isArgumentsLodash(1);
isArgumentsLodash(/x/);
isArgumentsLodash('a');
});
});

View File

@ -0,0 +1,25 @@
import { bench, describe } from 'vitest';
import { isObjectLike as isObjectLikeToolkit } from 'es-toolkit';
import { isObjectLike as isObjectLikeLodash } from 'lodash';
describe('isObjectLike', () => {
bench('es-toolkit/isObjectLike', () => {
isObjectLikeToolkit([1, 2, 3]);
isObjectLikeToolkit(true);
isObjectLikeToolkit(new Date());
isObjectLikeToolkit(new Error());
isObjectLikeToolkit(1);
isObjectLikeToolkit(/x/);
isObjectLikeToolkit('a');
});
bench('lodash/isObjectLike', () => {
isObjectLikeLodash([1, 2, 3]);
isObjectLikeLodash(true);
isObjectLikeLodash(new Date());
isObjectLikeLodash(new Error());
isObjectLikeLodash(1);
isObjectLikeLodash(/x/);
isObjectLikeLodash('a');
});
});

View File

@ -156,6 +156,7 @@ function sidebar(): DefaultTheme.Sidebar {
{
text: 'Predicates',
items: [
{ text: 'isArguments', link: '/reference/predicate/isArguments' },
{ text: 'isArray (compat)', link: '/reference/compat/predicate/isArray' },
{ text: 'isArrayLike', link: '/reference/predicate/isArrayLike' },
{ text: 'isEqual', link: '/reference/predicate/isEqual' },
@ -168,6 +169,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'isNil', link: '/reference/predicate/isNil' },
{ text: 'isNotNil', link: '/reference/predicate/isNotNil' },
{ text: 'isNull', link: '/reference/predicate/isNull' },
{ text: 'isObjectLike', link: '/reference/predicate/isObjectLike' },
{ text: 'isTypedArray', link: '/reference/predicate/isTypedArray' },
{ text: 'isUndefined', link: '/reference/predicate/isUndefined' },
],

View File

@ -167,6 +167,7 @@ function sidebar(): DefaultTheme.Sidebar {
{
text: '타입 가드',
items: [
{ text: 'isArguments', link: '/ko/reference/predicate/isArguments' },
{ text: 'isArray (호환성)', link: '/ko/reference/compat/predicate/isArray' },
{ text: 'isArrayLike', link: '/ko/reference/predicate/isArrayLike' },
{ text: 'isEqual', link: '/ko/reference/predicate/isEqual' },
@ -179,7 +180,8 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'isNil', link: '/ko/reference/predicate/isNil' },
{ text: 'isNotNil', link: '/ko/reference/predicate/isNotNil' },
{ text: 'isNull', link: '/ko/reference/predicate/isNull' },
{ text: 'isTypedArray', link: '/ko/reference/predicate/isTypedArray' },
{ text: 'isObjectLike', link: '/ko/reference/predicate/isObjectLike' },
{ text: 'isTypedArray', link: '/ko/reference/predicate/isNull' },
{
text: 'isUndefined',
link: '/ko/reference/predicate/isUndefined',

View File

@ -194,7 +194,7 @@ Even if a feature is marked "in review," it might already be under review to ens
| [eq](https://lodash.com/docs/4.17.15#eq) | ❌ |
| [gt](https://lodash.com/docs/4.17.15#gt) | ❌ |
| [gte](https://lodash.com/docs/4.17.15#gte) | ❌ |
| [isArguments](https://lodash.com/docs/4.17.15#isArguments) | |
| [isArguments](https://lodash.com/docs/4.17.15#isArguments) | |
| [isArray](https://lodash.com/docs/4.17.15#isArray) | ✅ |
| [isArrayBuffer](https://lodash.com/docs/4.17.15#isArrayBuffer) | ❌ |
| [isArrayLike](https://lodash.com/docs/4.17.15#isArrayLike) | ✅ |
@ -220,7 +220,7 @@ Even if a feature is marked "in review," it might already be under review to ens
| [isNull](https://lodash.com/docs/4.17.15#isNull) | ✅ |
| [isNumber](https://lodash.com/docs/4.17.15#isNumber) | ❌ |
| [isObject](https://lodash.com/docs/4.17.15#isObject) | ❌ |
| [isObjectLike](https://lodash.com/docs/4.17.15#isObjectLike) | |
| [isObjectLike](https://lodash.com/docs/4.17.15#isObjectLike) | |
| [isPlainObject](https://lodash.com/docs/4.17.15#isPlainObject) | ✅ |
| [isRegExp](https://lodash.com/docs/4.17.15#isRegExp) | ❌ |
| [isSafeInteger](https://lodash.com/docs/4.17.15#isSafeInteger) | ❌ |

View File

@ -195,7 +195,7 @@ chunk([1, 2, 3, 4], 0);
| [eq](https://lodash.com/docs/4.17.15#eq) | ❌ |
| [gt](https://lodash.com/docs/4.17.15#gt) | ❌ |
| [gte](https://lodash.com/docs/4.17.15#gte) | ❌ |
| [isArguments](https://lodash.com/docs/4.17.15#isArguments) | |
| [isArguments](https://lodash.com/docs/4.17.15#isArguments) | |
| [isArray](https://lodash.com/docs/4.17.15#isArray) | ✅ |
| [isArrayBuffer](https://lodash.com/docs/4.17.15#isArrayBuffer) | ❌ |
| [isArrayLike](https://lodash.com/docs/4.17.15#isArrayLike) | ✅ |
@ -221,7 +221,7 @@ chunk([1, 2, 3, 4], 0);
| [isNull](https://lodash.com/docs/4.17.15#isNull) | ✅ |
| [isNumber](https://lodash.com/docs/4.17.15#isNumber) | ❌ |
| [isObject](https://lodash.com/docs/4.17.15#isObject) | ❌ |
| [isObjectLike](https://lodash.com/docs/4.17.15#isObjectLike) | |
| [isObjectLike](https://lodash.com/docs/4.17.15#isObjectLike) | |
| [isPlainObject](https://lodash.com/docs/4.17.15#isPlainObject) | ❌ |
| [isRegExp](https://lodash.com/docs/4.17.15#isRegExp) | ❌ |
| [isSafeInteger](https://lodash.com/docs/4.17.15#isSafeInteger) | ❌ |

View File

@ -0,0 +1,42 @@
# isArguments
주어진 값이 `arguments` 객체인지 확인해요.
이 함수는 주어진 값이 `arguments` 객체이면 `true`, 아니면 `false`를 반환해요.
TypeScript의 타입 가드로 사용할 수 있어요. 파라미터로 주어진 값의 타입을 `IArguments`로 좁혀요.
## 인터페이스
```typescript
function isArguments(value?: unknown): value is IArguments;
```
### 파라미터
- `value` (`unknown`): `arguments` 객체인지 확인할 값이에요.
### 반환 값
(`value is IArguments`): 주어진 값이 `arguments` 객체이면 `true`, 아니면 `false`를 반환해요.
## 예시
## Examples
```typescript
import { isArguments } from 'es-toolkit/predicate';
const args = (function () {
return arguments;
})();
const strictArgs = (function () {
'use strict';
return arguments;
})();
const value = [1, 2, 3];
console.log(isArguments(args)); // true
console.log(isArguments(strictArgs)); // true
onsole.log(isArguments(value)); // false
```

View File

@ -0,0 +1,38 @@
# isObjectLike
주어진 값이 유사 객체인지 확인해요.
만약 주어진 값이 유사 객체이면 `true`, 아니면 `false`를 반환해요.
TypeScript의 타입 가드로 사용할 수 있어요. 파라미터로 주어진 값의 타입을 유사 객체로 좁혀요.
## 인터페이스
```typescript
export function isObjectLike<T>(value: T): value is Extract<T, object>;
```
### 파라미터
- `value` (`T`): 유사 객체인지 확인할 값이에요.
### 반환 값
(`value is Extract<T, object>`): 주어진 값이 유사 객체이면 `true`, 아니면 `false`를 반환해요.
## 예시
```typescript
import { isObjectLike } from 'es-toolkit/predicate';
const value1 = { a: 1 };
const value2 = [1, 2, 3];
const value3 = 'abc';
const value4 = () => {};
const value5 = null;
console.log(isObjectLike(value1)); // true
console.log(isObjectLike(value2)); // true
console.log(isObjectLike(value3)); // false
console.log(isObjectLike(value4)); // false
```

View File

@ -0,0 +1,40 @@
# isArguments
Check if a value is an arguments object.
It returns `true` if the value is an arguments object, and `false` otherwise.
This function can also serve as a type predicate in TypeScript, narrowing the type of the argument to an arguments object.
## Signature
```typescript
function isArguments(value?: unknown): value is IArguments;
```
### Parameters
- `value` (`unknown`): The value to test if it is an arguments object.
### Returns
(`value is IArguments`): Returns `true` if the value is an arguments, `false` otherwise.
## Examples
```typescript
import { isArguments } from 'es-toolkit/predicate';
const args = (function () {
return arguments;
})();
const strictArgs = (function () {
'use strict';
return arguments;
})();
const value = [1, 2, 3];
console.log(isArguments(args)); // true
console.log(isArguments(strictArgs)); // true
onsole.log(isArguments(value)); // false
```

View File

@ -0,0 +1,38 @@
# isObjectLike
Check if a value is an object-like array.
It returns `true` if the value is an object-like, and `false` otherwise.
This function can also serve as a type predicate in TypeScript, narrowing the type of the argument to an object-like.
## Signature
```typescript
export function isObjectLike<T>(value: T): value is Extract<T, object>;
```
### Parameters
- `value` (`T`): The value to test if it is an object-like.
### Returns
(`value is Extract<T, object>`): Returns `true` if the value is an object-like, `false` otherwise.
## Examples
```typescript
import { isObjectLike } from 'es-toolkit/predicate';
const value1 = { a: 1 };
const value2 = [1, 2, 3];
const value3 = 'abc';
const value4 = () => {};
const value5 = null;
console.log(isObjectLike(value1)); // true
console.log(isObjectLike(value2)); // true
console.log(isObjectLike(value3)); // false
console.log(isObjectLike(value4)); // false
```

View File

@ -194,7 +194,7 @@ chunk([1, 2, 3, 4], 0);
| [eq](https://lodash.com/docs/4.17.15#eq) | ❌ |
| [gt](https://lodash.com/docs/4.17.15#gt) | ❌ |
| [gte](https://lodash.com/docs/4.17.15#gte) | ❌ |
| [isArguments](https://lodash.com/docs/4.17.15#isArguments) | |
| [isArguments](https://lodash.com/docs/4.17.15#isArguments) | |
| [isArray](https://lodash.com/docs/4.17.15#isArray) | ✅ |
| [isArrayBuffer](https://lodash.com/docs/4.17.15#isArrayBuffer) | ❌ |
| [isArrayLike](https://lodash.com/docs/4.17.15#isArrayLike) | ✅ |
@ -220,7 +220,7 @@ chunk([1, 2, 3, 4], 0);
| [isNull](https://lodash.com/docs/4.17.15#isNull) | ✅ |
| [isNumber](https://lodash.com/docs/4.17.15#isNumber) | ❌ |
| [isObject](https://lodash.com/docs/4.17.15#isObject) | ❌ |
| [isObjectLike](https://lodash.com/docs/4.17.15#isObjectLike) | |
| [isObjectLike](https://lodash.com/docs/4.17.15#isObjectLike) | |
| [isPlainObject](https://lodash.com/docs/4.17.15#isPlainObject) | ❌ |
| [isRegExp](https://lodash.com/docs/4.17.15#isRegExp) | ❌ |
| [isSafeInteger](https://lodash.com/docs/4.17.15#isSafeInteger) | ❌ |

View File

@ -0,0 +1,23 @@
import { describe, it, expect } from 'vitest';
import { getTag } from './getTag';
describe('getTag function', () => {
it('should return the tag of the value', () => {
expect(getTag(null)).toBe('[object Null]');
expect(getTag(undefined)).toBe('[object Undefined]');
expect(getTag(1)).toBe('[object Number]');
expect(getTag('')).toBe('[object String]');
expect(getTag(true)).toBe('[object Boolean]');
expect(getTag(Symbol())).toBe('[object Symbol]');
expect(getTag([])).toBe('[object Array]');
expect(getTag({})).toBe('[object Object]');
expect(getTag(() => {})).toBe('[object Function]');
expect(getTag(new Date())).toBe('[object Date]');
expect(getTag(/./)).toBe('[object RegExp]');
});
it('should return the tag of the custom object', () => {
class Custom {}
expect(getTag(new Custom())).toBe('[object Object]');
});
});

View File

@ -0,0 +1,13 @@
/**
* Gets the `toStringTag` of `value`.
*
* @private
* @param {T} value The value to query.
* @returns {string} Returns the `Object.prototype.toString.call` result.
*/
export function getTag<T>(value: T) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]';
}
return Object.prototype.toString.call(value);
}

View File

@ -0,0 +1 @@
export const slice = Array.prototype.slice;

View File

@ -0,0 +1,8 @@
export const strictArgs = (function () {
'use strict';
// eslint-disable-next-line prefer-rest-params
return arguments;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-expect-error
})(1, 2, 3);

View File

@ -0,0 +1 @@
export const symbol = Symbol ? Symbol('a') : undefined;

View File

@ -9,8 +9,8 @@
*/
export function toArgs(array: unknown[]): IArguments {
// eslint-disable-next-line prefer-spread, @typescript-eslint/no-unused-vars
return (function (..._: any[]) {
return (function (..._: unknown[]) {
// eslint-disable-next-line prefer-rest-params
return arguments;
})(...array);
}
}

View File

@ -0,0 +1,35 @@
import { describe, it, expect } from 'vitest';
import { isArguments } from '../../predicate/isArguments';
import { args } from '../_internal/args';
import { stubFalse } from '../_internal/stubFalse';
import { falsey } from '../_internal/falsey';
import { symbol } from '../_internal/symbol';
import { slice } from '../_internal/slice';
import { strictArgs } from '../_internal/strictArgs';
import { noop } from '../../function';
describe('isArguments', () => {
it('should return `true` for `arguments` objects', () => {
expect(isArguments(args)).toBe(true);
expect(isArguments(strictArgs)).toBe(true);
});
it('should return `false` for non `arguments` objects', () => {
const expected = falsey.map(stubFalse);
const actual = falsey.map((value, index) => (index ? isArguments(value) : isArguments()));
expect(actual).toEqual(expected);
expect(isArguments([1, 2, 3])).toBe(false);
expect(isArguments(true)).toBe(false);
expect(isArguments(new Date())).toBe(false);
expect(isArguments(new Error())).toBe(false);
expect(isArguments(slice)).toBe(false);
expect(isArguments({ 0: 1, callee: noop, length: 1 })).toBe(false);
expect(isArguments(1)).toBe(false);
expect(isArguments(/x/)).toBe(false);
expect(isArguments('a')).toBe(false);
expect(isArguments(symbol)).toBe(false);
});
});

View File

@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { isObjectLike } from '../../predicate/isObjectLike';
import { args } from '../_internal/args';
import { falsey } from '../_internal/falsey';
import { symbol } from '../_internal/symbol';
import { slice } from '../_internal/slice';
import { stubFalse } from '../_internal/stubFalse';
describe('isObjectLike', () => {
it('should return `true` for objects', () => {
expect(isObjectLike(args)).toBe(true);
expect(isObjectLike([1, 2, 3])).toBe(true);
expect(isObjectLike(Object(false))).toBe(true);
expect(isObjectLike(new Date())).toBe(true);
expect(isObjectLike(new Error())).toBe(true);
expect(isObjectLike({ a: 1 })).toBe(true);
expect(isObjectLike(Object(0))).toBe(true);
expect(isObjectLike(/x/)).toBe(true);
expect(isObjectLike(Object('a'))).toBe(true);
});
it('should return `false` for non-objects', () => {
const values = falsey.concat(true, slice, 1, 'a', symbol);
const expected = values.map(stubFalse);
const actual = values.map(isObjectLike);
expect(actual).toEqual(expected);
});
});

View File

@ -9,3 +9,5 @@ export { isFunction } from './isFunction.ts';
export { isArrayLike } from './isArrayLike.ts';
export { isTypedArray } from './isTypedArray.ts';
export { isPrimitive } from './isPrimitive.ts';
export { isObjectLike } from './isObjectLike.ts';
export { isArguments } from './isArguments.ts';

View File

@ -0,0 +1,35 @@
import { describe, it, expect } from 'vitest';
import { isArguments } from './isArguments';
import { args } from '../compat/_internal/args';
import { stubFalse } from '../compat/_internal/stubFalse';
import { falsey } from '../compat/_internal/falsey';
import { symbol } from '../compat/_internal/symbol';
import { slice } from '../compat/_internal/slice';
import { strictArgs } from '../compat/_internal/strictArgs';
import { noop } from '../function';
describe('isArguments', () => {
it('should return `true` for `arguments` objects', () => {
expect(isArguments(args)).toBe(true);
expect(isArguments(strictArgs)).toBe(true);
});
it('should return `false` for non `arguments` objects', () => {
const expected = falsey.map(stubFalse);
const actual = falsey.map((value, index) => (index ? isArguments(value) : isArguments()));
expect(actual).toEqual(expected);
expect(isArguments([1, 2, 3])).toBe(false);
expect(isArguments(true)).toBe(false);
expect(isArguments(new Date())).toBe(false);
expect(isArguments(new Error())).toBe(false);
expect(isArguments(slice)).toBe(false);
expect(isArguments({ 0: 1, callee: noop, length: 1 })).toBe(false);
expect(isArguments(1)).toBe(false);
expect(isArguments(/x/)).toBe(false);
expect(isArguments('a')).toBe(false);
expect(isArguments(symbol)).toBe(false);
});
});

View File

@ -0,0 +1,25 @@
import { getTag } from '../compat/_internal/getTag';
/**
* Checks if the given value is an arguments object.
*
* This function tests whether the provided value is an arguments object or not.
* It returns `true` if the value is an arguments object, and `false` otherwise.
*
* This function can also serve as a type predicate in TypeScript, narrowing the type of the argument to an arguments object.
*
* @param {unknown} value - The value to test if it is an arguments object.
* @returns {value is IArguments} `true` if the value is an arguments, `false` otherwise.
*
* @example
* const args = (function() { return arguments; })();
* const strictArgs = (function() { 'use strict'; return arguments; })();
* const value = [1, 2, 3];
*
* console.log(isArguments(args)); // true
* console.log(isArguments(strictArgs)); // true
* console.log(isArguments(value)); // false
*/
export function isArguments(value?: unknown): value is IArguments {
return value !== null && typeof value === 'object' && getTag(value) === '[object Arguments]';
}

View File

@ -0,0 +1,30 @@
import { describe, it, expect } from 'vitest';
import { isObjectLike } from './isObjectLike';
import { args } from '../compat/_internal/args';
import { falsey } from '../compat/_internal/falsey';
import { symbol } from '../compat/_internal/symbol';
import { slice } from '../compat/_internal/slice';
import { stubFalse } from '../compat/_internal/stubFalse';
describe('isObjectLike', () => {
it('should return `true` for objects', () => {
expect(isObjectLike(args)).toBe(true);
expect(isObjectLike([1, 2, 3])).toBe(true);
expect(isObjectLike(Object(false))).toBe(true);
expect(isObjectLike(new Date())).toBe(true);
expect(isObjectLike(new Error())).toBe(true);
expect(isObjectLike({ a: 1 })).toBe(true);
expect(isObjectLike(Object(0))).toBe(true);
expect(isObjectLike(/x/)).toBe(true);
expect(isObjectLike(Object('a'))).toBe(true);
});
it('should return `false` for non-objects', () => {
const values = falsey.concat(true, slice, 1, 'a', symbol);
const expected = values.map(stubFalse);
const actual = values.map(isObjectLike);
expect(actual).toEqual(expected);
});
});

View File

@ -0,0 +1,28 @@
/**
* Checks if the given value is an object-like.
*
* This function tests whether the provided value is an object-like or not.
* It returns `true` if the value is an object-like, and `false` otherwise.
*
* This function can also serve as a type predicate in TypeScript, narrowing the type of the argument to an object-like.
*
* @template T - The type of value.
* @param {T} value - The value to test if it is an object-like.
* @returns {value is Extract<T, object>} `true` if the value is an object-like, `false` otherwise.
*
* @example
* const value1 = { a: 1 };
* const value2 = [1, 2, 3];
* const value3 = 'abc';
* const value4 = () => {};
* const value5 = null;
*
* console.log(isObjectLike(value1)); // true
* console.log(isObjectLike(value2)); // true
* console.log(isObjectLike(value3)); // false
* console.log(isObjectLike(value4)); // false
*/
export function isObjectLike<T>(value: T): value is Extract<T, object> {
return typeof value === 'object' && value !== null;
}