diff --git a/.scripts/docs/generate-docs.mts b/.scripts/docs/generate-docs.mts index eb3cda623..dbda636e6 100644 --- a/.scripts/docs/generate-docs.mts +++ b/.scripts/docs/generate-docs.mts @@ -120,7 +120,7 @@ function toDocumentationItems(docs: DocNode[]): DocumentationItems { }) .map(([name, entries]) => { const sourcePath = fileURLToPath(entries[0].location.filename); - const docPath: string = path.relative(basePath, sourcePath).replace(/.ts$/g, '.md'); + const docPath: string = path.join(path.dirname(path.relative(basePath, sourcePath)), `${name}.md`); return { docPath: docPath, diff --git a/CHANGELOG.md b/CHANGELOG.md index 721b10886..284686841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # es-toolkit Changelog +## Version v1.25.1 + +Released on October 15th, 2024. +- Resolved an issue in [cloneDeep](https://es-toolkit.slash.page/reference/object/cloneDeep.html) that incorrectly copied properties from the `target` when they were read-only. +- Updated [every](https://es-toolkit.slash.page/reference/compat/array/every.html), [filter](https://es-toolkit.slash.page/reference/compat/array/filter.html), [find](https://es-toolkit.slash.page/reference/compat/array/find.html), [findIndex](https://es-toolkit.slash.page/reference/compat/array/findIndex.html), [findLastIndex](https://es-toolkit.slash.page/reference/compat/array/findLastIndex.html), [indexOf](https://es-toolkit.slash.page/reference/compat/array/indexOf.html), and [join](https://es-toolkit.slash.page/reference/compat/array/join.html) to now accept array-like objects and a `fromIndex` parameter, making them compatible with lodash. + +This version includes contributions from @D-Sketon. Thank you for your valuable contributions! + +## Version v1.25.0 + +Released on October 14th, 2024. + +- Added support for [isFile](https://es-toolkit.slash.page/reference/predicate/isFile.html). +- Added compatibility functions for [escape](https://es-toolkit.slash.page/reference/string/escape.html), [toSafeInteger](https://es-toolkit.slash.page/reference/compat/util/toSafeInteger.html), [intersection](https://es-toolkit.slash.page/reference/array/intersection.html), [sample](https://es-toolkit.slash.page/reference/array/sample.html), [chunk](https://es-toolkit.slash.page/reference/array/chunk.html), [compact](https://es-toolkit.slash.page/reference/array/compact.html), [head](https://es-toolkit.slash.page/reference/array/head.html), [initial](https://es-toolkit.slash.page/reference/array/initial.html), [last](https://es-toolkit.slash.page/reference/array/last.html), [tail](https://es-toolkit.slash.page/reference/array/tail.html), [take](https://es-toolkit.slash.page/reference/array/take.html), [takeRight](https://es-toolkit.slash.page/reference/array/takeRight.html), [uniq](https://es-toolkit.slash.page/reference/array/uniq.html), and [without](https://es-toolkit.slash.page/reference/array/without.html). +- Enhanced performance for [at](https://es-toolkit.slash.page/reference/array/at.html) and [isPlainObject](https://es-toolkit.slash.page/reference/predicate/isPlainObject.html). +- Resolved an issue in [cloneDeep](https://es-toolkit.slash.page/reference/object/cloneDeep.html) that prevented it from cloning symbol properties and read-only properties of objects. +- Fixed a problem in [pick](https://es-toolkit.slash.page/reference/object/pick.html) within our compatibility library that incorrectly added `undefined` for keys that do not exist. + +This version includes contributions from @D-Sketon, @mass2527, @dayongkr, @lukaszkowalik2, @Gyumong, @Dohun-choi, @belgattitude, and @chhw130. Thank you for your valuable contributions! + ## Version v1.24.0 Released on October 7th, 2024. diff --git a/README-ja_jp.md b/README-ja_jp.md index 3e4728d9b..a1367300f 100644 --- a/README-ja_jp.md +++ b/README-ja_jp.md @@ -10,6 +10,7 @@ es-toolkitは、最先端のJavaScriptユーティリティライブラリであ - パフォーマンスを重視して設計されており、es-toolkitは最新のJavaScript環境で[2-3倍の性能向上](https://es-toolkit.slash.page/performance.html)を実現します。 - es-toolkitはツリーシェイキングをサポートしており、他のライブラリと比較してJavaScriptコードを最大[97%削減](https://es-toolkit.slash.page/bundle-size.html)します。 - es-toolkitには組み込みのTypeScriptサポートが含まれており、直感的で堅牢な型を提供します。また、[isNotNil](https://es-toolkit.slash.page/reference/predicate/isNotNil.html)などの便利な型ガードも提供します。 +- es-toolkitは、[Storybook](https://github.com/storybookjs/storybook/blob/9d862798d666678cc4822e857c00bbd744169ced/code/core/package.json#L358)や[ink](https://github.com/vadimdemedes/ink/blob/2090ad9779be59dea71d173eb49785b7bd4495d0/package.json#L55)など、よく使われるオープンソースライブラリで利用されています。 - es-toolkitは100%のテストカバレッジを持ち、信頼性と堅牢性を確保しています。 ## 例 diff --git a/README-ko_kr.md b/README-ko_kr.md index b4288698d..fac24dcdc 100644 --- a/README-ko_kr.md +++ b/README-ko_kr.md @@ -10,6 +10,7 @@ es-toolkit은 높은 성능과 작은 번들 사이즈, 강력한 타입을 자 - 설계할 때 퍼포먼스를 중요 원칙으로 두어, es-toolkit은 현대적인 JavaScript 환경에서 [2-3배 빠른 성능](https://es-toolkit.slash.page/ko/performance.html)을 보여요. - es-toolkit은 트리 셰이킹을 기본으로 지원하며, 함수에 따라서는 다른 라이브러리와 비교했을 때 [최대 97% 작은 JavaScript 번들 사이즈](https://es-toolkit.slash.page/ko/bundle-size.html)를 제공해요. - es-toolkit은 TypeScript 타입이 내장되어 있고, 직관적이고 정확한 타입을 추구해요. [isNotNil](https://es-toolkit.slash.page/ko/reference/predicate/isNotNil.html) 같은 사용하기 편리한 유틸리티 함수도 제공해요. +- es-toolkit은 [Storybook](https://github.com/storybookjs/storybook/blob/9d862798d666678cc4822e857c00bbd744169ced/code/core/package.json#L358)이나 [ink](https://github.com/vadimdemedes/ink/blob/2090ad9779be59dea71d173eb49785b7bd4495d0/package.json#L55) 같이 자주 사용되는 오픈소스 라이브러리에서 사용돼요. - es-toolkit은 100% 테스트 커버리지를 유지하면서, 높은 안정성을 자랑해요. ## 기여하기 diff --git a/README-zh_hans.md b/README-zh_hans.md index c255a2fdd..5e2176913 100644 --- a/README-zh_hans.md +++ b/README-zh_hans.md @@ -10,6 +10,7 @@ es-toolkit 是一个先进的、高性能的 JavaScript 实用工具库,具有 - 设计时考虑了性能,es-toolkit 在现代 JavaScript 环境中实现了 [2-3 倍的性能提升](https://es-toolkit.slash.page/zh_hans/performance.html)。 - es-toolkit 支持开箱即用,并且与其他库相比,可以将 JavaScript 代码减少高达 [97%](https://es-toolkit.slash.page/zh_hans/bundle-size.html)。 - es-toolkit 包含内置的 TypeScript 支持,提供直观且强大的类型。它还提供诸如 [isNotNil](https://es-toolkit.slash.page/zh_hans/reference/predicate/isNotNil.html) 等有用的类型保护。 +- es-toolkit 被多个流行的开源库所使用,例如 [Storybook](https://github.com/storybookjs/storybook/blob/9d862798d666678cc4822e857c00bbd744169ced/code/core/package.json#L358) 和 [ink](https://github.com/vadimdemedes/ink/blob/2090ad9779be59dea71d173eb49785b7bd4495d0/package.json#L55)。 - es-toolkit 经过了百分之百的测试覆盖率的实战检验,确保其可靠性和稳健性。 ## 示例 diff --git a/README.md b/README.md index 83b048bb1..c13dbb40a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ es-toolkit is a state-of-the-art, high-performance JavaScript utility library wi - Designed with performance in mind, es-toolkit achieves [2-3× better performance](https://es-toolkit.slash.page/performance.html) in modern JavaScript environments. - es-toolkit supports tree shaking out of the box, and [reduces JavaScript code by up to 97%](https://es-toolkit.slash.page/bundle-size.html) compared to other libraries. - es-toolkit includes built-in TypeScript support, with straightforward yet robust types. It also provides useful type guards such as [isNotNil](https://es-toolkit.slash.page/reference/predicate/isNotNil.html). +- es-toolkit is utilized by several popular open-source libraries, such as [Storybook](https://github.com/storybookjs/storybook/blob/9d862798d666678cc4822e857c00bbd744169ced/code/core/package.json#L358) and [ink](https://github.com/vadimdemedes/ink/blob/2090ad9779be59dea71d173eb49785b7bd4495d0/package.json#L55). - es-toolkit is battle-tested with 100% test coverage, ensuring reliability and robustness. ## Examples diff --git a/docs/index.md b/docs/index.md index bd6d9cb8a..cec4f5571 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,6 +26,8 @@ features: details: es-toolkit fully leverages modern JavaScript APIs for straightforward and error-free implementation. - title: Robust types details: es-toolkit offers simple yet robust types for all functions. + - title: Widely adopted + details: es-toolkit is widely adopted by popular open-source libraries such as Storybook and ink. - title: Battle-tested details: es-toolkit has 100% test coverage, ensuring maximum robustness. - title: Comprehensive runtime support diff --git a/docs/ja/index.md b/docs/ja/index.md index 841359ac8..989c5635c 100644 --- a/docs/ja/index.md +++ b/docs/ja/index.md @@ -26,6 +26,8 @@ features: details: 最新のJavaScript APIを使用しているため、実装が直感的でシンプルです。 - title: 堅牢な型定義 details: すべての関数に対してシンプルながら堅牢な型定義を提供しています。 + - title: 幅広い採用 + details: es-toolkitは、Storybookやinkなどの人気のオープンソースライブラリに広く採用されています。 - title: テストカバレッジ100% details: すべての関数とブランチに対して、綿密なテストが作成されています。 - title: あらゆる環境で使用可能 diff --git a/docs/ja/reference/compat/array/every.md b/docs/ja/reference/compat/array/every.md index 96e625a16..c3cf3b60d 100644 --- a/docs/ja/reference/compat/array/every.md +++ b/docs/ja/reference/compat/array/every.md @@ -37,6 +37,23 @@ function every>(object: T, doesMatch: string): - `arr` (`T[]`) または `object` (`T`): 検索する配列またはオブジェクト。 +::: info `arr` は `ArrayLike`、`null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`every` 関数は `arr` を次のように処理します: + +- `arr` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換されます。 +- `arr` が `null` または `undefined` の場合、空の配列として扱われます。 + +::: + +::: info `object` は `null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`every` 関数は `object` を次のように処理します: + +- `object` が `null` または `undefined` の場合、空のオブジェクトに変換されます。 + +::: + - `doesMatch`: - 配列の場合: diff --git a/docs/ja/reference/compat/array/filter.md b/docs/ja/reference/compat/array/filter.md index ffb8dbb19..64015b802 100644 --- a/docs/ja/reference/compat/array/filter.md +++ b/docs/ja/reference/compat/array/filter.md @@ -36,6 +36,15 @@ function filter>(object: T, doesMatch: string) - `arr` (`T[]`) または `object` (`T`): 繰り返し処理する配列やオブジェクト。 +::: info `arr` は `ArrayLike`、`null` または `undefined` になります + +lodash と完全に互換性を保つため、`filter` 関数は `arr` を次のように処理します。 + +- `arr` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換します。 +- `arr` が `null` または `undefined` の場合、空の配列として扱います。 + +::: + - `doesMatch`: - 配列の場合: diff --git a/docs/ja/reference/compat/array/find.md b/docs/ja/reference/compat/array/find.md index 2eed18171..6d53d4e9f 100644 --- a/docs/ja/reference/compat/array/find.md +++ b/docs/ja/reference/compat/array/find.md @@ -36,6 +36,23 @@ function find>(object: T, doesMatch: string): - `arr` (`T[]`) または `object` (`T`): 検索する配列またはオブジェクト。 +::: info `arr` は `ArrayLike` であるか、`null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`find` 関数は `arr` を次のように処理します。 + +- `arr` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換します。 +- `arr` が `null` または `undefined` の場合、空の配列と見なされます。 + +::: + +::: info `object` は `null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`find` 関数は `object` を次のように処理します。 + +- `object` が `null` または `undefined` の場合、空のオブジェクトに変換されます。 + +::: + - `doesMatch`: - 配列の場合: diff --git a/docs/ja/reference/compat/array/findIndex.md b/docs/ja/reference/compat/array/findIndex.md index 1e415534a..27c8d06a5 100644 --- a/docs/ja/reference/compat/array/findIndex.md +++ b/docs/ja/reference/compat/array/findIndex.md @@ -18,16 +18,25 @@ ## インターフェース ```typescript -function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown): number; -function findIndex(arr: T[], doesMatch: Partial): number; -function findIndex(arr: T[], doesMatch: [keyof T, unknown]): number; -function findIndex(arr: T[], doesMatch: string): number; +function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: Partial, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: string, fromIndex?: number): number; ``` ### パラメータ - `arr` (`T[]`): 検索する配列。 +::: info `arr` は `ArrayLike` であるか、`null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`findIndex` 関数は `arr` を次のように処理します。 + +- `arr` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換します。 +- `arr` が `null` または `undefined` の場合、空の配列と見なされます。 + +::: + - `doesMatch`: - **検査関数** (`(item: T, index: number, arr: T[]) => unknown`): 探している要素かどうかを返す関数。 @@ -35,6 +44,8 @@ function findIndex(arr: T[], doesMatch: string): number; - **プロパティ-値ペア** (`[keyof T, unknown]`): 最初が一致させるプロパティ、2番目が一致させる値を表すタプル。 - **プロパティ名** (`string`): 真と評価される値を持っているか確認するプロパティ名。 +- `fromIndex` (`number`): 検索を開始するインデックス。デフォルトは `0`。 + ### 戻り値 (`number`): 与えられた条件を満たす最初の要素のインデックス。ない場合は `-1`。 diff --git a/docs/ja/reference/compat/array/findLastIndex.md b/docs/ja/reference/compat/array/findLastIndex.md index cfdbff746..ba0929673 100644 --- a/docs/ja/reference/compat/array/findLastIndex.md +++ b/docs/ja/reference/compat/array/findLastIndex.md @@ -32,6 +32,15 @@ function findLastIndex(arr: T[], doesMatch: string, fromIndex?: number): numb - `arr` (`T[]`): 検索する配列。 +::: info `arr` は `ArrayLike` であるか、`null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`findLastIndex` 関数は `arr` を次のように処理します。 + +- `arr` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換します。 +- `arr` が `null` または `undefined` の場合、空の配列と見なされます。 + +::: + - `doesMatch`: - **検査関数** (`(item: T, index: number, arr: T[]) => unknown`): 探している要素かどうかを返す関数。 diff --git a/docs/ja/reference/compat/array/first.md b/docs/ja/reference/compat/array/first.md new file mode 100644 index 000000000..870c56161 --- /dev/null +++ b/docs/ja/reference/compat/array/first.md @@ -0,0 +1,50 @@ +# first + +::: info +この関数は互換性のために `es-toolkit/compat` からのみインポートできます。代替可能なネイティブ JavaScript API があるか、まだ十分に最適化されていないためです。 + +`es-toolkit/compat` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。 +::: + +配列の最初の要素を返します。配列が空の場合は`undefined`を返します。 + +この関数は配列を受け取り、配列の最初の要素を返します。 +配列が空の場合、関数は`undefined`を返します。 + +## インターフェース + +```typescript +function first(arr: ArrayLike | undefined | null): T | undefined; +``` + +### パラメータ + +- `arr` (`ArrayLike | undefined | null`): 最初の要素を取得するための配列です。 + +### 戻り値 + +(`T | undefined`): 配列の最初の要素、または配列が空の場合は`undefined`です。 + +## 例 + +```typescript +const arr1 = [1, 2, 3]; +const firstElement1 = first(arr1); +// firstElement1は1です。 + +const arr2: string[] = []; +const firstElement2 = first(arr2); +// firstElement2はundefinedです。 + +const arr3 = ['a', 'b', 'c']; +const firstElement3 = first(arr3); +// firstElement3は'a'です。 + +const arr4 = [true, false, true]; +const firstElement4 = first(arr4); +// firstElement4はtrueです。 + +const arr5: [number, string, boolean] = [1, 'a', true]; +const firstElement5 = first(arr5); +// firstElement5は1です。 +``` diff --git a/docs/ja/reference/compat/array/indexOf.md b/docs/ja/reference/compat/array/indexOf.md index a7614fca7..1a5df017d 100644 --- a/docs/ja/reference/compat/array/indexOf.md +++ b/docs/ja/reference/compat/array/indexOf.md @@ -14,12 +14,22 @@ ## インターフェース ```typescript -function indexOf(array: T[] | null | undefined, searchElement: T, fromIndex?: number): number; +function indexOf(array: T[], searchElement: T, fromIndex?: number): number; ``` ### パラメータ -- `array` (`T[] | null | undefined`): 検索対象の配列。 +- `array` (`T[]`): 検索対象の配列。 + +::: info `array` は `ArrayLike` または `null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`join` 関数は `array` を次のように処理します。 + +- `array` が `ArrayLike` の場合、配列に変換するために `Array.from(...)` を使用します。 +- `array` が `null` または `undefined` の場合、空の配列と見なされます。 + +::: + - `searchElement` (`T`): 検索する値。 - `fromIndex` (`number`, オプション): 検索を開始するインデックス。 diff --git a/docs/ja/reference/compat/array/join.md b/docs/ja/reference/compat/array/join.md index b270dabf6..dd426c503 100644 --- a/docs/ja/reference/compat/array/join.md +++ b/docs/ja/reference/compat/array/join.md @@ -11,12 +11,22 @@ ## インターフェース ```typescript -function join(array: T[], separator: string): string; +function join(array: T[], separator?: string): string; ``` ### パラメータ - `array` (`T[]`) - 結合する配列です。 + +::: info `array` は `ArrayLike` であるか、`null` または `undefined` である可能性があります + +lodash と完全に互換性があるように、`join` 関数は `array` を次のように処理します。 + +- `array` が `ArrayLike` の場合、`Array.from(...)` を使用して配列に変換します。 +- `array` が `null` または `undefined` の場合、空の配列と見なされます。 + +::: + - `separator` (`string`) - 要素を結合するために用いるセパレータ、デフォルトは一般的なセパレータ `,` です。 ### 戻り値 @@ -25,13 +35,8 @@ function join(array: T[], separator: string): string; ## 例 -````typescript ```typescript -const arr = ["a","b","c"]; -const result = join(arr, "~"); +const arr = ['a', 'b', 'c']; +const result = join(arr, '~'); console.log(result); // Output: "a~b~c" -```` - -``` - ``` diff --git a/docs/ja/reference/object/cloneDeep.md b/docs/ja/reference/object/cloneDeep.md index dffd7c4a7..355c367ae 100644 --- a/docs/ja/reference/object/cloneDeep.md +++ b/docs/ja/reference/object/cloneDeep.md @@ -44,6 +44,21 @@ console.log(clonedNestedObj.d === nestedObj.d); // false console.log(clonedNestedObj.d[2] === nestedObj.d[2]); // false ``` +### 読み取り専用プロパティ + +getterとして定義された読み取り専用プロパティを持つオブジェクトを深くコピーすると、新しくコピーされたオブジェクトはgetterの返す値を持ちます。 + +```tsx +const source = { + get value() { + return 3; + }, +}; + +const cloned = cloneDeep(source); +// cloned is now { value: 3 } +``` + ## デモ ::: sandpack diff --git a/docs/ko/index.md b/docs/ko/index.md index 4bc1e4d31..707e6278a 100644 --- a/docs/ko/index.md +++ b/docs/ko/index.md @@ -28,6 +28,8 @@ features: details: 모든 함수에 대해서 간단하지만 견고한 타입을 제공해요. - title: 테스트 커버리지 100% details: 모든 함수와 분기에 대해서 꼼꼼하게 테스트가 작성되어 있어요. + - title: 넓은 사용처 + details: es-toolkit은 Storybook 및 ink와 같은 인기 있는 오픈 소스 라이브러리에서 널리 사용되고 있어요. - title: 모든 환경에서 사용 가능 details: Node.js, Deno, Bun, 그리고 브라우저까지 넓은 JavaScript 환경을 지원해요. --- diff --git a/docs/ko/reference/compat/array/every.md b/docs/ko/reference/compat/array/every.md index 05d493bca..7da6ff849 100644 --- a/docs/ko/reference/compat/array/every.md +++ b/docs/ko/reference/compat/array/every.md @@ -37,6 +37,23 @@ function every>(object: T, doesMatch: string): - `arr` (`T[]`) 또는 `object` (`T`): 검색할 배열이나 객체. +::: info `arr`는 `ArrayLike`일 수도 있고, `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `every` 함수는 `arr`을 다음과 같이 처리해요: + +- `arr`가 `ArrayLike`인 경우 `Array.from(...)`을 사용하여 배열로 변환해요. +- `arr`가 `null` 또는 `undefined`인 경우 빈 배열로 간주돼요. + +::: + +::: info `object`는 `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `every` 함수는 `object`를 다음과 같이 처리해요: + +- `object`가 `null` 또는 `undefined`인 경우 빈 객체로 변환돼요. + +::: + - `doesMatch`: - 배열의 경우: diff --git a/docs/ko/reference/compat/array/filter.md b/docs/ko/reference/compat/array/filter.md index 67018a068..499ba6a99 100644 --- a/docs/ko/reference/compat/array/filter.md +++ b/docs/ko/reference/compat/array/filter.md @@ -36,6 +36,15 @@ function filter>(object: T, doesMatch: string) - `arr` (`T[]`) or `object` (`T`): 반복할 배열이나 객체. +::: info `arr`는 `ArrayLike`일 수도 있고, `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `filter` 함수는 `arr`을 다음과 같이 처리해요: + +- `arr`가 `ArrayLike`인 경우 `Array.from(...)`을 사용하여 배열로 변환해요. +- `arr`가 `null` 또는 `undefined`인 경우 빈 배열로 간주돼요. + +::: + - `doesMatch`: - 배열의 경우: diff --git a/docs/ko/reference/compat/array/find.md b/docs/ko/reference/compat/array/find.md index 9ef2ca85e..6ec9271ba 100644 --- a/docs/ko/reference/compat/array/find.md +++ b/docs/ko/reference/compat/array/find.md @@ -36,6 +36,23 @@ function find>(object: T, doesMatch: string): - `arr` (`T[]`) or `object` (`T`): 검색할 배열이나 객체. +::: info `arr`는 `ArrayLike`일 수도 있고, `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `find` 함수는 `arr`을 다음과 같이 처리해요: + +- `arr`가 `ArrayLike`인 경우 `Array.from(...)`을 사용하여 배열로 변환해요. +- `arr`가 `null` 또는 `undefined`인 경우 빈 배열로 간주돼요. + +::: + +::: info `object`는 `null` 또는 `undefined`일 수도 있어요 + +lodash와 완벽하게 호환되도록 `find` 함수는 `object`를 다음과 같이 처리해요: + +- `object`가 `null` 또는 `undefined`인 경우 빈 객체로 변환돼요. + +::: + - `doesMatch`: - 배열의 경우: diff --git a/docs/ko/reference/compat/array/findIndex.md b/docs/ko/reference/compat/array/findIndex.md index 1390ae054..27317b89b 100644 --- a/docs/ko/reference/compat/array/findIndex.md +++ b/docs/ko/reference/compat/array/findIndex.md @@ -18,16 +18,25 @@ ## 인터페이스 ```typescript -function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown): number; -function findIndex(arr: T[], doesMatch: Partial): number; -function findIndex(arr: T[], doesMatch: [keyof T, unknown]): number; -function findIndex(arr: T[], doesMatch: string): number; +function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: Partial, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: string, fromIndex?: number): number; ``` ### 파라미터 - `arr` (`T[]`): 검색할 배열. +::: info `arr`는 `ArrayLike`이거나 `null` 또는 `undefined`일 수 있어요 + +lodash와 완전히 호환되도록 `findIndex` 함수는 `arr`를 다음과 같이 처리해요. + +- `arr`가 `ArrayLike`인 경우, 배열로 변환하기 위해 `Array.from(...)`을 사용해요. +- `arr`가 `null` 또는 `undefined`인 경우, 빈 배열로 간주돼요. + +::: + - `doesMatch`: - **검사 함수** (`(item: T, index: number, arr: T[]) => unknown`): 찾는 요소인지 여부를 반환하는 함수. @@ -35,6 +44,8 @@ function findIndex(arr: T[], doesMatch: string): number; - **프로퍼티-값 쌍** (`[keyof T, unknown]`): 첫 번째가 일치시킬 프로퍼티, 두 번째가 일치시킬 값을 나타내는 튜플. - **프로퍼티 이름** (`string`): 참으로 평가되는 값을 가지고 있는지 확인할 프로퍼티 이름. +- `fromIndex` (`number`): 검색을 시작할 인덱스. 기본값은 `0`. + ### 반환 값 (`number`): 주어진 조건을 만족하는 첫 번째 요소의 인덱스. 없으면 `-1`. diff --git a/docs/ko/reference/compat/array/findLastIndex.md b/docs/ko/reference/compat/array/findLastIndex.md index 61af705c4..413ac17d2 100644 --- a/docs/ko/reference/compat/array/findLastIndex.md +++ b/docs/ko/reference/compat/array/findLastIndex.md @@ -22,16 +22,25 @@ function findLastIndex( arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown, fromIndex?: number -): T | undefined; -function findLastIndex(arr: T[], doesMatch: Partial, fromIndex?: number): T | undefined; -function findLastIndex(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): T | undefined; -function findLastIndex(arr: T[], doesMatch: string, fromIndex?: number): T | undefined; +): number; +function findLastIndex(arr: T[], doesMatch: Partial, fromIndex?: number): number; +function findLastIndex(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): number; +function findLastIndex(arr: T[], doesMatch: string, fromIndex?: number): number; ``` ### 파라미터 - `arr` (`T[]`): 검색할 배열. +::: info `arr`은 `ArrayLike`일 수도 있고, `null` 또는 `undefined`일 수도 있어요 + +lodash와 완전히 호환되도록 `findLastIndex` 함수는 `arr`를 다음과 같이 처리해요. + +- `arr`가 `ArrayLike`인 경우, 배열로 변환하기 위해 `Array.from(...)`을 사용해요. +- `arr`가 `null` 또는 `undefined`인 경우, 빈 배열로 간주돼요. + +::: + - `doesMatch`: - **검사 함수** (`(item: T, index: number, arr: T[]) => unknown`): 찾는 요소인지 여부를 반환하는 함수. diff --git a/docs/ko/reference/compat/array/first.md b/docs/ko/reference/compat/array/first.md new file mode 100644 index 000000000..5682dd977 --- /dev/null +++ b/docs/ko/reference/compat/array/first.md @@ -0,0 +1,49 @@ +# first + +::: info +이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요. + +`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요. +::: + +배열의 첫 번째 요소를 반환해요. + +이 함수는 배열을 입력받아 배열의 첫 번째 요소를 반환해요. 배열이 비어 있는 경우, 함수는 `undefined`를 반환해요. + +## 인터페이스 + +```typescript +function first(arr: ArrayLike | undefined | null): T | undefined; +``` + +### 파라미터 + +- `arr` (`T[]`): 첫 번째 요소를 가져올 배열. + +### 반환 값 + +(`T | undefined`): 배열의 첫 번째 요소, 배열이 비어 있는 경우 `undefined`. + +## 예시 + +```typescript +const arr1 = [1, 2, 3]; +const firstElement1 = first(arr1); +// firstElement1은 1이에요. + +const arr2: string[] = []; +const firstElement2 = first(arr2); +// firstElement2는 undefined에요. + +const arr3 = ['a', 'b', 'c']; +const firstElement3 = first(arr3); +// firstElement3는 'a'이에요. + +const arr4 = [true, false, true]; +const firstElement4 = first(arr4); +// firstElement4는 true에요. + +const arr5: [number, string, boolean] = [1, 'a', true]; +const firstElement5 = first(arr5); +// firstElement5는 1이에요. +``` diff --git a/docs/ko/reference/compat/array/indexOf.md b/docs/ko/reference/compat/array/indexOf.md index 0f32aa105..193f402c8 100644 --- a/docs/ko/reference/compat/array/indexOf.md +++ b/docs/ko/reference/compat/array/indexOf.md @@ -14,12 +14,22 @@ ## 인터페이스 ```typescript -function indexOf(array: T[] | null | undefined, searchElement: T, fromIndex?: number): number; +function indexOf(array: T[], searchElement: T, fromIndex?: number): number; ``` ### 파라미터 -- `array` (`T[] | null | undefined`): 검색할 배열. +- `array` (`T[]`): 검색할 배열. + +::: info `array`는 `ArrayLike`이거나 `null` 또는 `undefined`일 수 있어요 + +lodash와 완전히 호환되도록 `indexOf` 함수는 `array`를 다음과 같이 처리해요. + +- `array`가 `ArrayLike`인 경우, 배열로 변환하기 위해 `Array.from(...)`을 사용해요. +- `array`가 `null` 또는 `undefined`인 경우, 빈 배열로 간주돼요. + +::: + - `searchElement` (`T`): 찾을 값. - `fromIndex` (`number`, 선택): 검색을 시작할 인덱스. diff --git a/docs/ko/reference/compat/array/join.md b/docs/ko/reference/compat/array/join.md index 0bec74413..963d4e14c 100644 --- a/docs/ko/reference/compat/array/join.md +++ b/docs/ko/reference/compat/array/join.md @@ -11,12 +11,22 @@ ## 인터페이스 ```typescript -function join(array: T[], separator: string): string; +function join(array: T[], separator?: string): string; ``` ### 파라미터 - `array` (`T[]`) - 결합할 배열이에요. + +::: info `array`는 `ArrayLike`이거나 `null` 또는 `undefined`일 수 있어요 + +lodash와 완전히 호환되도록 `join` 함수는 `array`를 다음과 같이 처리해요. + +- `array`가 `ArrayLike`인 경우, 배열로 변환하기 위해 `Array.from(...)`을 사용해요. +- `array`가 `null` 또는 `undefined`인 경우, 빈 배열로 간주돼요. + +::: + - `separator` (`string`) - 요소를 결합하는 데 사용하는 구분자. 기본값은 쉼표(,)에요. ### 반환 값 diff --git a/docs/ko/reference/object/cloneDeep.md b/docs/ko/reference/object/cloneDeep.md index 0b48286d7..65513d003 100644 --- a/docs/ko/reference/object/cloneDeep.md +++ b/docs/ko/reference/object/cloneDeep.md @@ -10,11 +10,11 @@ function cloneDeep(obj: T): T; ### 파라미터 -- `obj` (`T`): 복사할 객체예요. +- `obj` (`T`): 복사할 객체. ### 반환 값 -(`T`): 주어진 객체의 깊은 복사본이에요. +(`T`): 주어진 객체의 깊은 복사본. ## 예시 @@ -44,6 +44,21 @@ console.log(clonedNestedObj.d === nestedObj.d); // false console.log(clonedNestedObj.d[2] === nestedObj.d[2]); // false ``` +### 읽기 전용 프로퍼티 + +접근자(getter)로 정의된 읽기 전용 프로퍼티가 있는 객체를 깊은 복사하면, 새로 복사된 객체는 접근자의 반환 값을 값으로 가져요. + +```tsx +const source = { + get value() { + return 3; + }, +}; + +const cloned = cloneDeep(source); +// cloned is now { value: 3 } +``` + ## 데모 ::: sandpack diff --git a/docs/reference/compat/array/every.md b/docs/reference/compat/array/every.md index 970ee67de..df52b82f8 100644 --- a/docs/reference/compat/array/every.md +++ b/docs/reference/compat/array/every.md @@ -37,6 +37,23 @@ function every>(object: T, doesMatch: string): - `arr` (`T[]`) or `object` (`T`): The array or object to search through. +::: info `arr` can be `ArrayLike`, `null`, or `undefined` + +To ensure full compatibility with lodash, the every function handles `arr` in this way: + +- If arr is an `ArrayLike`, it gets converted into an array using `Array.from(...)`. +- If arr is `null` or `undefined`, it will be treated as an empty array. + +::: + +::: info `object` can be `null` or `undefined` + +To ensure full compatibility with lodash, the every function handles `object` in this way: + +- If `object` is `null` or `undefined`, it will be converted into an empty object. + +::: + - `doesMatch`: - For the first `every` overload with arrays: diff --git a/docs/reference/compat/array/filter.md b/docs/reference/compat/array/filter.md index 06755f72b..c446e4a04 100644 --- a/docs/reference/compat/array/filter.md +++ b/docs/reference/compat/array/filter.md @@ -36,6 +36,15 @@ function filter>(object: T, doesMatch: string) - `arr` (`T[]`) or `object` (`T`): The array or object to iterate over. +::: info `arr` can be `ArrayLike`, `null`, or `undefined` + +To ensure full compatibility with lodash, the `filter` function handles `arr` in this way: + +- If `arr` is an `ArrayLike`, it gets converted into an array using `Array.from(...)`. +- If `arr` is `null` or `undefined`, it will be treated as an empty array. + +::: + - `doesMatch`: - For the first `filter` overload with arrays: diff --git a/docs/reference/compat/array/find.md b/docs/reference/compat/array/find.md index 499334631..fd9e9bc37 100644 --- a/docs/reference/compat/array/find.md +++ b/docs/reference/compat/array/find.md @@ -36,6 +36,23 @@ function find>(object: T, doesMatch: string): - `arr` (`T[]`) or `object` (`T`): The array or object to search through. +::: info `arr` can be `ArrayLike`, `null`, or `undefined` + +To ensure full compatibility with lodash, the `find` function handles `arr` in this way: + +- If `arr` is an `ArrayLike`, it gets converted into an array using `Array.from(...)`. +- If `arr` is `null` or `undefined`, it will be treated as an empty array. + +::: + +::: info `object` can be `null` or `undefined` + +To ensure full compatibility with lodash, the `find` function handles `object` in this way: + +- If `object` is `null` or `undefined`, it will be converted into an empty object. + +::: + - `doesMatch`: - For the first `find` overload with arrays: diff --git a/docs/reference/compat/array/findIndex.md b/docs/reference/compat/array/findIndex.md index a1e0d8681..ba86b271b 100644 --- a/docs/reference/compat/array/findIndex.md +++ b/docs/reference/compat/array/findIndex.md @@ -18,16 +18,25 @@ You can specify the condition in several ways: ## Signature ```typescript -function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown): number; -function findIndex(arr: T[], doesMatch: Partial): number; -function findIndex(arr: T[], doesMatch: [keyof T, unknown]): number; -function findIndex(arr: T[], doesMatch: string): number; +function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: Partial, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: string, fromIndex?: number): number; ``` ### Parameters - `arr` (`T[]`): The array to search through. +::: info `arr` can be `ArrayLike` or `null` or `undefined` + +To ensure full compatibility with lodash, the `findIndex` function processes `arr` as follows: + +- If `arr` is `ArrayLike`, it converts it to an array using `Array.from(...)`. +- If `arr` is `null` or `undefined`, it is treated as an empty array. + +::: + - `doesMatch`: - **Predicate function** (`(item: T, index: number, arr: readonly T[]) => unknown`): A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. @@ -35,6 +44,8 @@ function findIndex(arr: T[], doesMatch: string): number; - **Property-value pair** (`[keyof T, unknown]`): An array where the first element is the property key and the second element is the value to match. - **Property name** (`string`): The name of the property to check for a truthy value. +- `fromIndex` (`number`): The index to start the search from, defaults to `0`. + ### Returns (`number`): The index of the first item that has the specified property value, or `-1` if no match is found. diff --git a/docs/reference/compat/array/findLastIndex.md b/docs/reference/compat/array/findLastIndex.md index 8a7cb38ac..1a67d2e93 100644 --- a/docs/reference/compat/array/findLastIndex.md +++ b/docs/reference/compat/array/findLastIndex.md @@ -32,6 +32,15 @@ function findLastIndex(arr: T[], doesMatch: string, fromIndex?: number): numb - `arr` (`T[]`): The array to search through. +::: info `arr` can be `ArrayLike` or `null` or `undefined` + +To ensure full compatibility with lodash, the `findLastIndex` function processes `arr` as follows: + +- If `arr` is `ArrayLike`, it converts it to an array using `Array.from(...)`. +- If `arr` is `null` or `undefined`, it is treated as an empty array. + +::: + - `doesMatch`: - **Predicate function** (`(item: T, index: number, arr: readonly T[]) => unknown`): A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. diff --git a/docs/reference/compat/array/first.md b/docs/reference/compat/array/first.md new file mode 100644 index 000000000..88f39127f --- /dev/null +++ b/docs/reference/compat/array/first.md @@ -0,0 +1,50 @@ +# first + +::: info +This function is only available in `es-toolkit/compat` for compatibility reasons. It either has alternative native JavaScript APIs or isn’t fully optimized yet. + +When imported from `es-toolkit/compat`, it behaves exactly like lodash and provides the same functionalities, as detailed [here](../../../compatibility.md). +::: + +Returns the first element of an array or `undefined` if the array is empty. + +This function takes an array and returns the first element of the array. +If the array is empty, the function returns `undefined`. + +## Signature + +```typescript +function first(arr: ArrayLike | undefined | null): T | undefined; +``` + +### Parameters + +- `arr` (`ArrayLike | undefined | null`): The array from which to get the first element. + +### Returns + +(`T | undefined`): The first element of the array, or `undefined` if the array is empty. + +## Examples + +```typescript +const arr1 = [1, 2, 3]; +const firstElement1 = first(arr1); +// firstElement1 will be 1 + +const arr2: string[] = []; +const firstElement2 = first(arr2); +// firstElement2 will be undefined + +const arr3 = ['a', 'b', 'c']; +const firstElement3 = first(arr3); +// firstElement3 will be 'a' + +const arr4 = [true, false, true]; +const firstElement4 = first(arr4); +// firstElement4 will be true + +const arr5: [number, string, boolean] = [1, 'a', true]; +const firstElement5 = first(arr5); +// firstElement5 will be 1 +``` diff --git a/docs/reference/compat/array/indexOf.md b/docs/reference/compat/array/indexOf.md index f1010a2e5..1f59938bc 100644 --- a/docs/reference/compat/array/indexOf.md +++ b/docs/reference/compat/array/indexOf.md @@ -14,12 +14,22 @@ It uses strict equality (`===`) to compare elements other than `NaN`. ## Signature ```typescript -function indexOf(array: T[] | null | undefined, searchElement: T, fromIndex?: number): number; +function indexOf(array: T[], searchElement: T, fromIndex?: number): number; ``` ### Parameters -- `array` (`T[] | null | undefined`): The array to search. +- `array` (`T[]`): The array to search. + +::: info `array` can be `ArrayLike` or `null` or `undefined` + +To ensure full compatibility with lodash, the `indexOf` function processes `array` as follows: + +- If `array` is `ArrayLike`, it converts it to an array using `Array.from(...)`. +- If `array` is `null` or `undefined`, it is treated as an empty array. + +::: + - `searchElement` (`T`): The value to search for. - `fromIndex` (`number`, optional): The index to start the search at. diff --git a/docs/reference/compat/array/join.md b/docs/reference/compat/array/join.md index 5a8f29a98..dab5521e4 100644 --- a/docs/reference/compat/array/join.md +++ b/docs/reference/compat/array/join.md @@ -11,12 +11,22 @@ Joins elements of an array into a string. ## Signature ```typescript -function join(array: T[], separator: string): string; +function join(array: T[], separator?: string): string; ``` ### Parameters - `array` (`T[]`) - The array to join. + +::: info `array` can be `ArrayLike` or `null` or `undefined` + +To ensure full compatibility with lodash, the `join` function processes `array` as follows: + +- If `array` is `ArrayLike`, it converts it to an array using `Array.from(...)`. +- If `array` is `null` or `undefined`, it is treated as an empty array. + +::: + - `separator` (`string`) - The separator used to join the elements. Defaults to `,`. ### Returns diff --git a/docs/reference/object/cloneDeep.md b/docs/reference/object/cloneDeep.md index 9d3e9d53d..3181a0d85 100644 --- a/docs/reference/object/cloneDeep.md +++ b/docs/reference/object/cloneDeep.md @@ -44,6 +44,21 @@ console.log(clonedNestedObj.d === nestedObj.d); // false console.log(clonedNestedObj.d[2] === nestedObj.d[2]); // false ``` +### Read-only properties + +When you clone an object that has read-only properties defined by getters, the values produced by those getters will be included in the new copied object. + +```tsx +const source = { + get value() { + return 3; + }, +}; + +const cloned = cloneDeep(source); +// cloned is now { value: 3 } +``` + ## Demo ::: sandpack diff --git a/docs/zh_hans/index.md b/docs/zh_hans/index.md index 6032b4da8..f6b440127 100644 --- a/docs/zh_hans/index.md +++ b/docs/zh_hans/index.md @@ -28,6 +28,8 @@ features: details: es-toolkit为所有函数提供简单而强大的类型支持。 - title: 经过实战验证 details: es-toolkit具有100%的测试覆盖率,确保最大的健壮性。 + - title: 广泛采用 + details: es-toolkit被多个流行的开源库广泛采用,例如Storybook和ink。 - title: 全面的运行时支持 details: es-toolkit支持包括Node.js、Deno、Bun和浏览器在内的所有JavaScript环境。 --- diff --git a/docs/zh_hans/reference/compat/array/every.md b/docs/zh_hans/reference/compat/array/every.md index dd3b8394b..2399fc0d3 100644 --- a/docs/zh_hans/reference/compat/array/every.md +++ b/docs/zh_hans/reference/compat/array/every.md @@ -38,6 +38,23 @@ function every>(object: T, doesMatch: string): - `arr` (`T[]`) 或 `object` (`T`): 要搜索的数组或对象。 +::: info `arr` 可以是 `ArrayLike`、`null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`every` 函数会按照以下方式处理 `arr`: + +- 如果 `arr` 是 `ArrayLike`,它将使用 `Array.from(...)` 转换为数组。 +- 如果 `arr` 是 `null` 或 `undefined`,它将被视为一个空数组。 + +::: + +::: info `object` 可以是 `null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`every` 函数会按照以下方式处理 `object`: + +- 如果 `object` 是 `null` 或 `undefined`,它将被转换为一个空对象。 + +::: + - `doesMatch`: - 对于数组的 `every` 重载: diff --git a/docs/zh_hans/reference/compat/array/filter.md b/docs/zh_hans/reference/compat/array/filter.md index e73b274bd..2aa3559ab 100644 --- a/docs/zh_hans/reference/compat/array/filter.md +++ b/docs/zh_hans/reference/compat/array/filter.md @@ -37,6 +37,15 @@ function filter>(object: T, doesMatch: string) - `arr` (`T[]`) 或 `object` (`T`): 要迭代的数组或对象。 +::: info `arr` 可以是 `ArrayLike`、`null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`filter` 函数会按照以下方式处理 `arr`: + +- 如果 `arr` 是 `ArrayLike`,它将使用 `Array.from(...)` 转换为数组。 +- 如果 `arr` 是 `null` 或 `undefined`,它将被视为一个空数组。 + +::: + - `doesMatch`: - 对于数组的第一个 `filter` 重载: diff --git a/docs/zh_hans/reference/compat/array/find.md b/docs/zh_hans/reference/compat/array/find.md index 2c45bac49..41fcf1a34 100644 --- a/docs/zh_hans/reference/compat/array/find.md +++ b/docs/zh_hans/reference/compat/array/find.md @@ -37,6 +37,23 @@ function find>(object: T, doesMatch: string): - `arr` (`T[]`) 或 `object` (`T`): 要搜索的数组或对象。 +::: info `arr` 可以是 `ArrayLike`、`null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`find` 函数会按照以下方式处理 `arr`: + +- 如果 `arr` 是 `ArrayLike`,它将使用 `Array.from(...)` 转换为数组。 +- 如果 `arr` 是 `null` 或 `undefined`,它将被视为一个空数组。 + +::: + +::: info `object` 可以是 `null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`find` 函数会按照以下方式处理 `object`: + +- 如果 `object` 是 `null` 或 `undefined`,它将被转换为一个空对象。 + +::: + - `doesMatch`: - 对于数组的 `find` 重载: diff --git a/docs/zh_hans/reference/compat/array/findIndex.md b/docs/zh_hans/reference/compat/array/findIndex.md index f89bd9687..ca9f19be3 100644 --- a/docs/zh_hans/reference/compat/array/findIndex.md +++ b/docs/zh_hans/reference/compat/array/findIndex.md @@ -19,16 +19,25 @@ ## 签名 ```typescript -function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown): number; -function findIndex(arr: T[], doesMatch: Partial): number; -function findIndex(arr: T[], doesMatch: [keyof T, unknown]): number; -function findIndex(arr: T[], doesMatch: string): number; +function findIndex(arr: T[], doesMatch: (item: T, index: number, arr: T[]) => unknown, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: Partial, fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: [keyof T, unknown], fromIndex?: number): number; +function findIndex(arr: T[], doesMatch: string, fromIndex?: number): number; ``` ### 参数 - `arr` (`T[]`): 要搜索的数组。 +::: info `arr` 可能是 `ArrayLike`,也可能是 `null` 或 `undefined` + +为了与 lodash 完全兼容,`findIndex` 函数会对 `arr` 进行如下处理: + +- 如果 `arr` 是 `ArrayLike`,则会使用 `Array.from(...)` 将其转换为数组。 +- 如果 `arr` 是 `null` 或 `undefined`,则会将其视为空数组。 + +::: + - `doesMatch`: - **谓词函数** (`(item: T, index: number, arr: T[]) => unknown`): 一个函数,接受项、其索引和数组,如果项符合条件则返回真值。 @@ -36,6 +45,8 @@ function findIndex(arr: T[], doesMatch: string): number; - **属性-值对** (`[keyof T, unknown]`): 一个数组,第一个元素是属性键,第二个元素是要匹配的值。 - **属性名称** (`string`): 要检查其真值的属性名称。 +- `fromIndex` (`number`): 搜索开始的位置。默认为 `0`。 + ### 返回 (`number`): 第一个具有指定属性值的项的索引,如果没有找到匹配项,则为 `-1`。 diff --git a/docs/zh_hans/reference/compat/array/findLastIndex.md b/docs/zh_hans/reference/compat/array/findLastIndex.md index 2437e9db0..b1287d1fb 100644 --- a/docs/zh_hans/reference/compat/array/findLastIndex.md +++ b/docs/zh_hans/reference/compat/array/findLastIndex.md @@ -33,6 +33,15 @@ function findLastIndex(arr: T[], doesMatch: string, fromIndex?: number): numb - `arr` (`T[]`): 要搜索的数组。 +::: info `arr` 可能是 `ArrayLike`,也可能是 `null` 或 `undefined` + +为了与 lodash 完全兼容,`findLastIndex` 函数会对 `arr` 进行如下处理。 + +- 如果 `arr` 是 `ArrayLike`,则会使用 `Array.from(...)` 将其转换为数组。 +- 如果 `arr` 是 `null` 或 `undefined`,则会将其视为空数组。 + +::: + - `doesMatch`: - **谓词函数** (`(item: T, index: number, arr: T[]) => unknown`): 一个函数,接受项、其索引和数组,如果项符合条件则返回真值。 diff --git a/docs/zh_hans/reference/compat/array/first.md b/docs/zh_hans/reference/compat/array/first.md new file mode 100644 index 000000000..06a288ca6 --- /dev/null +++ b/docs/zh_hans/reference/compat/array/first.md @@ -0,0 +1,50 @@ +# first + +::: info +出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。 + +从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。 +::: + +返回数组的第一个元素,如果数组为空,则返回`undefined`。 + +此函数接受一个数组并返回数组的第一个元素。 +如果数组为空,函数将返回`undefined`。 + +## 签名 + +```typescript +function first(arr: ArrayLike | undefined | null): T | undefined; +``` + +### 参数 + +- `arr` (`ArrayLike | undefined | null`): 获取第一个元素的数组。 + +### 返回值 + +(`T | undefined`): 数组的第一个元素,如果数组为空,则为`undefined`。 + +## 示例 + +```typescript +const arr1 = [1, 2, 3]; +const firstElement1 = first(arr1); +// firstElement1 将是 1 + +const arr2: string[] = []; +const firstElement2 = first(arr2); +// firstElement2 将是 undefined + +const arr3 = ['a', 'b', 'c']; +const firstElement3 = first(arr3); +// firstElement3 将是 'a' + +const arr4 = [true, false, true]; +const firstElement4 = first(arr4); +// firstElement4 将是 true + +const arr5: [number, string, boolean] = [1, 'a', true]; +const firstElement5 = first(arr5); +// firstElement5 将是 1 +``` diff --git a/docs/zh_hans/reference/compat/array/indexOf.md b/docs/zh_hans/reference/compat/array/indexOf.md index 8b053453b..91fa3ba8c 100644 --- a/docs/zh_hans/reference/compat/array/indexOf.md +++ b/docs/zh_hans/reference/compat/array/indexOf.md @@ -15,12 +15,22 @@ ## 签名 ```typescript -function indexOf(array: T[] | null | undefined, searchElement: T, fromIndex?: number): number; +function indexOf(array: T[], searchElement: T, fromIndex?: number): number; ``` ### 参数 -- `array` (`T[] | null | undefined`): 要搜索的数组。 +- `array` (`T[]`): 要搜索的数组。 + +::: info `array` 可以是 `ArrayLike` 或 `null` 或 `undefined` 。 + +为了确保与 lodash 的完全兼容性,`indexOf` 函数会按照以下方式处理 `array`: + +- 如果 `array` 是 `ArrayLike`,它会使用 `Array.from(...)` 将其转换为数组。 +- 如果 `array` 是 `null` 或 `undefined`,它会被视为一个空数组。 + +::: + - `searchElement` (`T`): 要搜索的值。 - `fromIndex` (`number`, 可选): 开始搜索的索引。 diff --git a/docs/zh_hans/reference/compat/array/join.md b/docs/zh_hans/reference/compat/array/join.md index c164f36ea..44a04f1a8 100644 --- a/docs/zh_hans/reference/compat/array/join.md +++ b/docs/zh_hans/reference/compat/array/join.md @@ -12,12 +12,22 @@ ## 签名 ```typescript -function join(array: T[], separator: string): string; +function join(array: T[], separator?: string): string; ``` ### 参数 - `array` (`T[]`) - 要连接的数组。 + +::: info `array` 可以是 `ArrayLike` 或 `null` 或 `undefined` + +为了确保与 lodash 的完全兼容性,`join` 函数会按照以下方式处理 `array`: + +- 如果 `array` 是 `ArrayLike`,它会使用 `Array.from(...)` 将其转换为数组。 +- 如果 `array` 是 `null` 或 `undefined`,它会被视为一个空数组。 + +::: + - `separator` (`string`) - 用于分隔元素的分隔符,默认是常用分隔符`,`。 ### 返回值 diff --git a/docs/zh_hans/reference/object/cloneDeep.md b/docs/zh_hans/reference/object/cloneDeep.md index 3ccf43256..3e52f9a3d 100644 --- a/docs/zh_hans/reference/object/cloneDeep.md +++ b/docs/zh_hans/reference/object/cloneDeep.md @@ -44,6 +44,21 @@ console.log(clonedNestedObj.d === nestedObj.d); // false console.log(clonedNestedObj.d[2] === nestedObj.d[2]); // false ``` +### 读取属性 + +当你克隆一个具有由 getter 定义的只读属性的对象时,这些 getter 产生的值将包含在新复制的对象中。 + +```tsx +const source = { + get value() { + return 3; + }, +}; + +const cloned = cloneDeep(source); +// cloned is now { value: 3 } +``` + ## 演示 ::: sandpack diff --git a/jsr.json b/jsr.json index 2916cacd8..31db90685 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@es-toolkit/es-toolkit", - "version": "1.24.0", + "version": "1.25.1", "exports": { ".": "./src/index.ts", "./compat": "./src/compat/index.ts" diff --git a/package.json b/package.json index c0e4a4396..2b16ca7b4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "es-toolkit", "description": "A state-of-the-art, high-performance JavaScript utility library with a small bundle size and strong type annotations.", - "version": "1.24.0", + "version": "1.25.1", "homepage": "https://es-toolkit.slash.page", "bugs": "https://github.com/toss/es-toolkit/issues", "repository": { @@ -186,4 +186,4 @@ "format": "prettier --write .", "transform": "jscodeshift -t ./.scripts/tests/transform-lodash-test.ts $0 && prettier --write $0" } -} +} \ No newline at end of file diff --git a/src/compat/array/every.spec.ts b/src/compat/array/every.spec.ts index 5c61e9b3f..a6b70d589 100644 --- a/src/compat/array/every.spec.ts +++ b/src/compat/array/every.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { every } from './every'; +import { args } from '../_internal/args'; import { empties } from '../_internal/empties'; import { identity } from '../_internal/identity'; import { stubFalse } from '../_internal/stubFalse'; @@ -134,4 +135,15 @@ describe('every', () => { const actual = [[1]].map(every); expect(actual).toEqual([true]); }); + + it('should return true when provided `null` or `undefined`', () => { + expect(every(null, identity)).toBe(true); + expect(every(undefined, identity)).toBe(true); + }); + + it('should support array-like objects', () => { + expect(every({ 0: 'a', 1: 'b', length: 2 }, stubFalse)).toBe(false); + expect(every('123', stubFalse)).toBe(false); + expect(every(args, stubFalse)).toBe(false); + }); }); diff --git a/src/compat/array/every.ts b/src/compat/array/every.ts index 26ae449bf..59998099b 100644 --- a/src/compat/array/every.ts +++ b/src/compat/array/every.ts @@ -7,7 +7,7 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * Checks if all elements in an array are truthy. * * @template T - * @param {T[]} arr - The array to check through. + * @param {ArrayLike | null | undefined} arr - The array to check through. * @returns {boolean} - `true` if all elements are truthy, or `false` if at least one element is falsy. * * @example @@ -19,13 +19,13 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * const resultWithFalsy = every(itemsWithFalsy); * console.log(resultWithFalsy); // false */ -export function every(arr: readonly T[]): boolean; +export function every(arr: ArrayLike | null | undefined): boolean; /** * Checks if every item in an array matches the given predicate function. * * @template T - * @param {T[]} arr - The array to check through. + * @param {ArrayLike | null | undefined} arr - The array to check through. * @param {(item: T, index: number, arr: T[]) => unknown} doesMatch - A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. * @returns {boolean} - `true` if every item matches the predicate, or `false` if at least one item does not match. * @@ -35,13 +35,16 @@ export function every(arr: readonly T[]): boolean; * const result = every(items, (item) => item > 0); * console.log(result); // true */ -export function every(arr: readonly T[], doesMatch: (item: T, index: number, arr: readonly T[]) => unknown): boolean; +export function every( + arr: ArrayLike | null | undefined, + doesMatch: (item: T, index: number, arr: readonly T[]) => unknown +): boolean; /** * Checks if every item in an array matches the given partial object. * * @template T - * @param {T[]} arr - The array to check through. + * @param {ArrayLike | null | undefined} arr - The array to check through. * @param {Partial} doesMatch - A partial object that specifies the properties to match. * @returns {boolean} - `true` if every item matches the partial object, or `false` if at least one item does not match. * @@ -51,13 +54,13 @@ export function every(arr: readonly T[], doesMatch: (item: T, index: number, * const result = every(items, { name: 'Bob' }); * console.log(result); // false */ -export function every(arr: readonly T[], doesMatch: Partial): boolean; +export function every(arr: ArrayLike | null | undefined, doesMatch: Partial): boolean; /** * Checks if every item in an array matches a property with a specific value. * * @template T - * @param {T[]} arr - The array to check through. + * @param {ArrayLike | null | undefined} arr - The array to check through. * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. * @returns {boolean} - `true` if every item has the specified property value, or `false` if at least one item does not match. * @@ -67,13 +70,13 @@ export function every(arr: readonly T[], doesMatch: Partial): boolean; * const result = every(items, ['name', 'Alice']); * console.log(result); // false */ -export function every(arr: readonly T[], doesMatchProperty: [keyof T, unknown]): boolean; +export function every(arr: ArrayLike | null | undefined, doesMatchProperty: [keyof T, unknown]): boolean; /** * Checks if every item in an array has a specific property, where the property name is provided as a string. * * @template T - * @param {T[]} arr - The array to check through. + * @param {ArrayLike | null | undefined} arr - The array to check through. * @param {string} propertyToCheck - The property name to check. * @returns {boolean} - `true` if every item has the specified property, or `false` if at least one item does not match. * @@ -83,13 +86,13 @@ export function every(arr: readonly T[], doesMatchProperty: [keyof T, unknown * const result = every(items, 'name'); * console.log(result); // true */ -export function every(arr: readonly T[], propertyToCheck: string): boolean; +export function every(arr: ArrayLike | null | undefined, propertyToCheck: string): boolean; /** * Checks if every item in an object matches the given predicate function. * * @template T - * @param {T} object - The object to check through. + * @param {T | null | undefined} object - The object to check through. * @param {(value: T[keyof T], key: keyof T, object: T) => unknown} doesMatch - A function that takes an value, its key, and the object, and returns a truthy value if the item matches the criteria. * @returns {boolean} - `true` if every property value matches the predicate, or `false` if at least one does not match. * @@ -100,7 +103,7 @@ export function every(arr: readonly T[], propertyToCheck: string): boolean; * console.log(result); // true */ export function every>( - object: T, + object: T | null | undefined, doesMatch: (value: T[keyof T], key: keyof T, object: T) => unknown ): boolean; @@ -108,7 +111,7 @@ export function every>( * Checks if every item in an object matches the given partial value. * * @template T - * @param {T} object - The object to check through. + * @param {T | null | undefined} object - The object to check through. * @param {Partial} doesMatch - A partial value to match against the values of the object. * @returns {boolean} - `true` if every property value matches the partial value, or `false` if at least one does not match. * @@ -118,13 +121,16 @@ export function every>( * const result = every(obj, { name: 'Bob' }); * console.log(result); // false */ -export function every>(object: T, doesMatch: Partial): boolean; +export function every>( + object: T | null | undefined, + doesMatch: Partial +): boolean; /** * Checks if every item in an object matches a property with a specific value. * * @template T - * @param {T[]} object - The object to check through. + * @param {T | null | undefined} object - The object to check through. * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. * @returns {boolean} - `true` if every item has the specified property value, or `false` if at least one item does not match. * @@ -134,13 +140,16 @@ export function every>(object: T, doesMatch: P * const result = every(obj, ['name', 'Alice']); * console.log(result); // false */ -export function every>(object: T, doesMatchProperty: [keyof T, unknown]): boolean; +export function every>( + object: T | null | undefined, + doesMatchProperty: [keyof T, unknown] +): boolean; /** * Checks if every item in an object has a specific property, where the property name is provided as a string. * * @template T - * @param {T} object - The object to check through. + * @param {T | null | undefined} object - The object to check through. * @param {string} propertyToCheck - The property name to check. * @returns {boolean} - `true` if every property value has the specified property, or `false` if at least one does not match. * @@ -150,14 +159,17 @@ export function every>(object: T, doesMatchPro * const result = every(obj, 'name'); * console.log(result); // true */ -export function every>(object: T, propertyToCheck: string): boolean; +export function every>( + object: T | null | undefined, + propertyToCheck: string +): boolean; /** * Checks if every item in an object has a specific property, where the property name is provided as a string. * * @template T * @param {T extends Record ? T : never} object - The object to check through. - * @param {readonly T[] | Record} source - The source array or object to check through. + * @param {ArrayLike | Record | null | undefined} source - The source array or object to check through. * @param {((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string} doesMatch - The criteria to match. It can be a function, a partial object, a key-value pair, or a property name. * @param {string} propertyToCheck - The property name to check. * @returns {boolean} - `true` if every property value has the specified property, or `false` if at least one does not match. @@ -169,18 +181,14 @@ export function every>(object: T, propertyToCh * console.log(result); // true */ export function every( - source: readonly T[] | Record, + source: ArrayLike | Record | null | undefined, doesMatch?: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string ): boolean { if (!source) { - source = []; + return true; } - let values = source; - - if (!Array.isArray(source)) { - values = Object.values(source); - } + const values = Array.isArray(source) ? source : Object.values(source); if (!doesMatch) { doesMatch = identity; diff --git a/src/compat/array/fill.spec.ts b/src/compat/array/fill.spec.ts index da32c6184..192219768 100644 --- a/src/compat/array/fill.spec.ts +++ b/src/compat/array/fill.spec.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from 'vitest'; import { fill } from './fill.ts'; +import { args } from '../_internal/args'; import { falsey } from '../_internal/falsey'; +import { toArgs } from '../_internal/toArgs.ts'; describe('fill', () => { it('should use a default `start` of `0` and a default `end` of `length`', () => { @@ -32,6 +34,7 @@ describe('fill', () => { const expected = falsey.map(() => ['a', 'a', 'a']); const actual = falsey.map(start => { const array = [1, 2, 3]; + // @ts-expect-error testing invalid input return fill(array, 'a', start); }); @@ -73,6 +76,7 @@ describe('fill', () => { const expected = falsey.map(value => (value === undefined ? ['a', 'a', 'a'] : [1, 2, 3])); const actual = falsey.map(end => { const array = [1, 2, 3]; + // @ts-expect-error testing invalid input return fill(array, 'a', 0, end); }); @@ -95,6 +99,7 @@ describe('fill', () => { const positions = [[0.1, 1.6], ['0', 1], [0, '1'], ['1'], [NaN, 1], [1, NaN]]; const actual = positions.map(pos => { const array = [1, 2, 3]; + // @ts-expect-error testing invalid input return fill(array, 'a', pos[0], pos[1]); }); @@ -120,4 +125,22 @@ describe('fill', () => { [1, 1], ]); }); + + it('should return an empty array when provided `null` or `undefined`', () => { + expect(fill(null, 'a')).toEqual([]); + expect(fill(undefined, 'a')).toEqual([]); + }); + + it('should return an empty array when provided none array-like object', () => { + // @ts-expect-error - invalid argument + expect(fill(1, 'a')).toEqual([]); + // @ts-expect-error - invalid argument + expect(fill(true, 'a')).toEqual([]); + }); + + it('should support array-like objects', () => { + expect(fill({ 0: 1, 1: 2, length: 2 }, 3)).toEqual({ 0: 3, 1: 3, length: 2 }); + expect(fill('12', '3')).toEqual('12'); + expect(fill(args, 3)).toEqual(toArgs([3, 3, 3])); + }); }); diff --git a/src/compat/array/fill.ts b/src/compat/array/fill.ts index 86d0fa3c9..e16b719f1 100644 --- a/src/compat/array/fill.ts +++ b/src/compat/array/fill.ts @@ -1,4 +1,6 @@ import { fill as fillToolkit } from '../../array/fill.ts'; +import { isArrayLike } from '../predicate/isArrayLike.ts'; +import { isString } from '../predicate/isString.ts'; /** * Fills the whole array with a specified value. @@ -7,10 +9,10 @@ import { fill as fillToolkit } from '../../array/fill.ts'; * start index up to the end index (non-inclusive). If the start or end indices are not provided, it defaults to filling the * entire array. * - * @template T, U, S, V - * @param {Array} array - The array to fill. - * @param {U} value - The value to fill the array with. - * @returns {Array} The array with the filled values. + * @template T + * @param {unknown[] | null | undefined} array - The array to fill. + * @param {T} value - The value to fill the array with. + * @returns {T[]} The array with the filled values. * * @example * const array = [1, 2, 3]; @@ -26,18 +28,46 @@ import { fill as fillToolkit } from '../../array/fill.ts'; * const result = fill(array, '*', -2, -1); * // => [1, '*', 3] */ -export function fill(array: unknown[], value?: T): T[]; +export function fill(array: unknown[] | null | undefined, value?: T): T[]; /** - * Fills elements of an array with a specified value from the start position to the end. + * Fills the whole array with a specified value. * * This function mutates the original array and replaces its elements with the provided value, starting from the specified * start index up to the end index (non-inclusive). If the start or end indices are not provided, it defaults to filling the * entire array. * - * @template T, U, S, V - * @param {Array} array - The array to fill. + * @template T + * @param {ArrayLike | null | undefined} array - The array to fill. + * @param {T} value - The value to fill the array with. + * @returns {ArrayLike} The array with the filled values. + * + * @example + * const array = [1, 2, 3]; + * const result = fill(array, 'a'); + * // => ['a', 'a', 'a'] + * + * const result = fill(Array(3), 2); + * // => [2, 2, 2] + * + * const result = fill([4, 6, 8, 10], '*', 1, 3); + * // => [4, '*', '*', 10] + * + * const result = fill(array, '*', -2, -1); + * // => [1, '*', 3] + */ +export function fill(array: ArrayLike | null | undefined, value?: T): ArrayLike; +/** + * Fills elements of an array with a specified value from the start position up to, but not including, the end position. + * + * This function mutates the original array and replaces its elements with the provided value, starting from the specified + * start index up to the end index (non-inclusive). If the start or end indices are not provided, it defaults to filling the + * entire array. + * + * @template T, U + * @param {Array | null | undefined} array - The array to fill. * @param {U} value - The value to fill the array with. - * @param {S} [start=0] - The start position. Defaults to 0. + * @param {number} [start=0] - The start position. Defaults to 0. + * @param {number} [end=arr.length] - The end position. Defaults to the array's length. * @returns {Array} The array with the filled values. * * @example @@ -54,7 +84,12 @@ export function fill(array: unknown[], value?: T): T[]; * const result = fill(array, '*', -2, -1); * // => [1, '*', 3] */ -export function fill(array: Array, value: U, start: S): Array; +export function fill( + array: Array | null | undefined, + value: U, + start?: number, + end?: number +): Array; /** * Fills elements of an array with a specified value from the start position up to, but not including, the end position. * @@ -62,12 +97,12 @@ export function fill(array: Array, value: U, start: S): Array} array - The array to fill. + * @template T, U + * @param {ArrayLike | null | undefined} array - The array to fill. * @param {U} value - The value to fill the array with. - * @param {S} [start=0] - The start position. Defaults to 0. - * @param {V} [end=arr.length] - The end position. Defaults to the array's length. - * @returns {Array} The array with the filled values. + * @param {number} [start=0] - The start position. Defaults to 0. + * @param {number} [end=arr.length] - The end position. Defaults to the array's length. + * @returns {ArrayLike} The array with the filled values. * * @example * const array = [1, 2, 3]; @@ -83,7 +118,12 @@ export function fill(array: Array, value: U, start: S): Array [1, '*', 3] */ -export function fill(array: Array, value: U, start: S, end: V): Array; +export function fill( + array: ArrayLike | null | undefined, + value: U, + start?: number, + end?: number +): ArrayLike; /** * Fills elements of an array with a specified value from the start position up to, but not including, the end position. * @@ -91,12 +131,12 @@ export function fill(array: Array, value: U, start: S, end: V * start index up to the end index (non-inclusive). If the start or end indices are not provided, it defaults to filling the * entire array. * - * @template T, U, S, V - * @param {Array} array - The array to fill. + * @template T, U + * @param {ArrayLike | null | undefined} array - The array to fill. * @param {U} value - The value to fill the array with. - * @param {S} [start=0] - The start position. Defaults to 0. - * @param {V} [end=arr.length] - The end position. Defaults to the array's length. - * @returns {Array} The array with the filled values. + * @param {number} [start=0] - The start position. Defaults to 0. + * @param {number} [end=arr.length] - The end position. Defaults to the array's length. + * @returns {ArrayLike} The array with the filled values. * * @example * const array = [1, 2, 3]; @@ -112,7 +152,19 @@ export function fill(array: Array, value: U, start: S, end: V * const result = fill(array, '*', -2, -1); * // => [1, '*', 3] */ -export function fill(array: Array, value: U, start = 0, end = array.length): Array { +export function fill( + array: ArrayLike | null | undefined, + value: U, + start = 0, + end = array ? array.length : 0 +): ArrayLike { + if (!isArrayLike(array)) { + return []; + } + if (isString(array)) { + // prevent TypeError: Cannot assign to read only property of string + return array; + } start = Math.floor(start); end = Math.floor(end); @@ -123,5 +175,5 @@ export function fill(array: Array, value: U, start = 0, end = array end = 0; } - return fillToolkit(array, value, start, end); + return fillToolkit(array as any, value, start, end); } diff --git a/src/compat/array/filter.spec.ts b/src/compat/array/filter.spec.ts index c908fac7f..2ed50313c 100644 --- a/src/compat/array/filter.spec.ts +++ b/src/compat/array/filter.spec.ts @@ -1,10 +1,15 @@ import { describe, expect, it } from 'vitest'; import { filter } from './filter'; +import { args } from '../_internal/args'; function isEven(n: number) { return n % 2 === 0; } +function isEven2(n: string) { + return parseInt(n) % 2 === 0; +} + describe('filter', () => { it('It should return an empty array when no predicate is provided.', () => { const arr = [1, 2, 3]; @@ -119,4 +124,22 @@ describe('filter', () => { { id: 2, name: 'Bob' }, ]); }); + + it('should return `[]` when provided `null` or `undefined`', () => { + expect(filter(null as any, isEven)).toEqual([]); + expect(filter(undefined as any, isEven)).toEqual([]); + }); + + it('should return `[]` when provided none array-like object', () => { + expect(filter(1 as any, isEven)).toEqual([]); + expect(filter('' as any, isEven)).toEqual([]); + expect(filter(true as any, isEven)).toEqual([]); + expect(filter(Symbol() as any, isEven)).toEqual([]); + }); + + it('should support array-like objects', () => { + expect(filter({ 0: 1, 1: 2, 2: 3, length: 3 }, isEven)).toEqual([2]); + expect(filter('123', isEven2)).toEqual(['2']); + expect(filter(args, isEven)).toEqual([2]); + }); }); diff --git a/src/compat/array/filter.ts b/src/compat/array/filter.ts index a094c9c1a..296319fdc 100644 --- a/src/compat/array/filter.ts +++ b/src/compat/array/filter.ts @@ -8,7 +8,7 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * Filters items from a array and returns an array of elements. * * @template T - * @param {T[]} arr - The array to iterate over. + * @param {ArrayLike | null | undefined} arr - The array to iterate over. * @param {(item: T, index: number, arr: T[]) => unknown} doesMatch - The function invoked per iteration. * @returns {T[]} - Returns a new array of elements that satisfy the given doesMatch function. * @@ -16,13 +16,16 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * filter([1, 2, 3], n => n % 2 === 0) * // => [2] */ -export function filter(arr: readonly T[], doesMatch?: (item: T, index: number, arr: readonly T[]) => unknown): T[]; +export function filter( + arr: ArrayLike | null | undefined, + doesMatch?: (item: T, index: number, arr: readonly T[]) => unknown +): T[]; /** * Filters elements in a arr that match the properties of the given partial object. * * @template T - * @param {T[]} arr - The array to iterate over. + * @param {ArrayLike | null | undefined} arr - The array to iterate over. * @param {Partial} doesMatch - A partial object that specifies the properties to match. * @returns {T[]} - Returns a new array of elements that match the given properties. * @@ -31,13 +34,13 @@ export function filter(arr: readonly T[], doesMatch?: (item: T, index: number * filter(arr, { name: 'Bob' }); * // => [{ id: 2, name: 'Bob' }] */ -export function filter(arr: readonly T[], doesMatch: Partial): T[]; +export function filter(arr: ArrayLike | null | undefined, doesMatch: Partial): T[]; /** * Filters elements in a arr that match the given key-value pair. * * @template T - * @param {T[]} arr - The array to iterate over. + * @param {ArrayLike | null | undefined} arr - The array to iterate over. * @param {[keyof T, unknown]} doesMatchProperty - The key-value pair to match. * @returns {T[]} - Returns a new array of elements that match the given key-value pair. * @@ -46,13 +49,13 @@ export function filter(arr: readonly T[], doesMatch: Partial): T[]; * filter(arr, ['name', 'Alice']); * // => [{ id: 1, name: 'Alice' }] */ -export function filter(arr: readonly T[], doesMatchProperty: [keyof T, unknown]): T[]; +export function filter(arr: ArrayLike | null | undefined, doesMatchProperty: [keyof T, unknown]): T[]; /** * Filters the arr, returning elements that contain the given property name. * * @template T - * @param {T[]} arr - The array to iterate over. + * @param {ArrayLike | null | undefined} arr - The array to iterate over. * @param {string} propertyToCheck - The property name to check. * @returns {T[]} - Returns a new array of elements that match the given property name. * @@ -61,7 +64,7 @@ export function filter(arr: readonly T[], doesMatchProperty: [keyof T, unknow * filter(arr, 'name'); * // => [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }] */ -export function filter(arr: readonly T[], propertyToCheck: string): T[]; +export function filter(arr: ArrayLike | null | undefined, propertyToCheck: string): T[]; /** * Filters items from a object and returns an array of elements that match the given predicate function. @@ -81,7 +84,7 @@ export function filter(arr: readonly T[], propertyToCheck: string): T[]; * // => [3] */ export function filter>( - object: T, + object: T | null | undefined, doesMatch: (value: T[keyof T], key: keyof T, object: T) => unknown ): T[]; @@ -89,7 +92,7 @@ export function filter>( * Filters elements in a object that match the properties of the given partial object. * * @template T - * @param {T} object - The object to iterate over. + * @param {T | null | undefined} object - The object to iterate over. * @param {Partial} doesMatch - The partial object to match * @returns {T[]} - Returns a new array of elements that match the given properties.pair. * @@ -98,13 +101,16 @@ export function filter>( * filter(obj, { name: 'Bob' }); * // => [{ id: 2, name: 'Bob' }] */ -export function filter>(object: T, doesMatch: Partial): T[]; +export function filter>( + object: T | null | undefined, + doesMatch: Partial +): T[]; /** * Filters elements in a arr that match the given key-value pair. * * @template T - * @param {T} object - The object to iterate over. + * @param {T | null | undefined} object - The object to iterate over. * @param {[keyof T, unknown]} doesMatchProperty - The key-value pair to match. * @returns {T[]} - Returns a new array of elements that match the given key-value pair. * @@ -113,13 +119,16 @@ export function filter>(object: T, doesMatch: * filter(obj, ['name', 'Alice']); * // => [{ id: 1, name: 'Alice' }] */ -export function filter>(object: T, doesMatchProperty: [keyof T, unknown]): T[]; +export function filter>( + object: T | null | undefined, + doesMatchProperty: [keyof T, unknown] +): T[]; /** * Filters the object, returning elements that contain the given property name. * * @template T - * @param {T} object - The object to iterate over. + * @param {T | null | undefined} object - The object to iterate over. * @param {string} propertyToCheck - The property name to check. * @returns {T[]} - Returns a new array of elements that match the given property name. * @@ -128,14 +137,14 @@ export function filter>(object: T, doesMatchPr * filter(obj, 'name'); * // => [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }] */ -export function filter>(object: T, propertyToCheck: string): T[]; +export function filter>(object: T | null | undefined, propertyToCheck: string): T[]; /** * Iterates over the collection and filters elements based on the given predicate. * If a function is provided, it is invoked for each element in the collection. * * @template T - * @param {T[] | Record} source - The array or object to iterate over. + * @param {ArrayLike | Record | null | undefined} source - The array or object to iterate over. * @param {((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string} [predicate=identity] - The function invoked per iteration. * @returns {T[]} - Returns a new array of filtered elements that satisfy the predicate. * @@ -153,9 +162,12 @@ export function filter>(object: T, propertyToC * // => [{ a: 2 }] */ export function filter( - source: T[] | Record, + source: ArrayLike | Record | null | undefined, predicate?: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string ): T[] { + if (!source) { + return []; + } if (!predicate) { predicate = identity; } @@ -170,7 +182,7 @@ export function filter( for (let i = 0; i < keys.length; i++) { const key = keys[i]; - const value = source[key]; + const value = source[key] as T; if (predicate(value, key as number, source)) { result.push(value); diff --git a/src/compat/array/find.spec.ts b/src/compat/array/find.spec.ts index 71f43141c..99dbe1597 100644 --- a/src/compat/array/find.spec.ts +++ b/src/compat/array/find.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { find } from './find'; +import { args } from '../_internal/args'; import { empties } from '../_internal/empties'; import { slice } from '../_internal/slice'; @@ -75,4 +76,22 @@ describe('find', () => { expect(args).toEqual([1, 'a', object]); }); + + it('should return `undefined` when provided `null` or `undefined`', () => { + expect(find(null, 'a')).toBe(undefined); + expect(find(undefined, 'a')).toBe(undefined); + }); + + it('should return `undefined` when provided none array-like object', () => { + // @ts-expect-error - invalid argument + expect(find(1, 'a')).toBe(undefined); + // @ts-expect-error - invalid argument + expect(find(true, 'a')).toBe(undefined); + }); + + it('should support array-like objects', () => { + expect(find({ 0: 1, 1: 2, 2: 3, length: 3 }, i => i === 3)).toBe(3); + expect(find('123', i => i === '3')).toBe('3'); + expect(find(args, i => i === 3)).toBe(3); + }); }); diff --git a/src/compat/array/find.ts b/src/compat/array/find.ts index 571231e2b..53c089003 100644 --- a/src/compat/array/find.ts +++ b/src/compat/array/find.ts @@ -6,7 +6,7 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * Finds the first item in an array that matches the given predicate function. * * @template T - * @param {T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {(item: T, index: number, arr: T[]) => unknown} doesMatch - A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. * @returns {T | undefined} - The first item that matches the predicate, or `undefined` if no match is found. * @@ -17,7 +17,7 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * console.log(result); // 4 */ export function find( - arr: readonly T[], + arr: ArrayLike | null | undefined, doesMatch: (item: T, index: number, arr: readonly T[]) => unknown ): T | undefined; @@ -25,7 +25,7 @@ export function find( * Finds the first item in an array that matches the given partial object. * * @template T - * @param {T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {Partial} doesMatch - A partial object that specifies the properties to match. * @returns {T | undefined} - The first item that matches the partial object, or `undefined` if no match is found. * @@ -35,13 +35,13 @@ export function find( * const result = find(items, { name: 'Bob' }); * console.log(result); // { id: 2, name: 'Bob' } */ -export function find(arr: readonly T[], doesMatch: Partial): T | undefined; +export function find(arr: ArrayLike | null | undefined, doesMatch: Partial): T | undefined; /** * Finds the first item in an array that matches a property with a specific value. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. * @returns {T | undefined} - The first item that has the specified property value, or `undefined` if no match is found. * @@ -51,13 +51,13 @@ export function find(arr: readonly T[], doesMatch: Partial): T | undefined * const result = find(items, ['name', 'Alice']); * console.log(result); // { id: 1, name: 'Alice' } */ -export function find(arr: readonly T[], doesMatchProperty: [keyof T, unknown]): T | undefined; +export function find(arr: ArrayLike | null | undefined, doesMatchProperty: [keyof T, unknown]): T | undefined; /** * Finds the first item in an array that has a specific property, where the property name is provided as a string. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {string} propertyToCheck - The property name to check. * @returns {T | undefined} - The first item that has the specified property, or `undefined` if no match is found. * @@ -67,13 +67,13 @@ export function find(arr: readonly T[], doesMatchProperty: [keyof T, unknown] * const result = find(items, 'name'); * console.log(result); // { id: 1, name: 'Alice' } */ -export function find(arr: readonly T[], propertyToCheck: string): T | undefined; +export function find(arr: ArrayLike | null | undefined, propertyToCheck: string): T | undefined; /** * Finds the first item in an object that matches the given predicate function. * * @template T - * @param {T extends Record ? T : never} object - The object to search through. + * @param {T extends Record ? T | null | undefined: never} object - The object to search through. * @param {(item: T[keyof T], index: number, arr: T) => unknown} doesMatch - A function that takes an item, its key, and the object, and returns a truthy value if the item matches the criteria. * @returns {T | undefined} - The first property value that matches the predicate, or `undefined` if no match is found. * @@ -84,7 +84,7 @@ export function find(arr: readonly T[], propertyToCheck: string): T | undefin * console.log(result); // 3 */ export function find>( - object: T, + object: T | null | undefined, doesMatch: (item: T[keyof T], index: keyof T, object: T) => unknown ): T | undefined; @@ -92,7 +92,7 @@ export function find>( * Finds the first item in an object that matches the given partial value. * * @template T - * @param {T extends Record ? T : never} object - The object to search through. + * @param {T extends Record ? T | null | undefined: never} object - The object to search through. * @param {Partial} doesMatch - A partial value to match against the values of the object. * @returns {T | undefined} - The first property value that matches the partial value, or `undefined` if no match is found. * @@ -102,13 +102,16 @@ export function find>( * const result = find(obj, { name: 'Bob' }); * console.log(result); // { id: 2, name: 'Bob' } */ -export function find>(object: T, doesMatch: Partial): T | undefined; +export function find>( + object: T | null | undefined, + doesMatch: Partial +): T | undefined; /** * Finds the first item in an object that matches a property with a specific value. * * @template T - * @param {readonly T[]} object - The object to search through. + * @param {T extends Record ? T | null | undefined: never} object - The object to search through. * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. * @returns {T | undefined} - The first item that has the specified property value, or `undefined` if no match is found. * @@ -119,7 +122,7 @@ export function find>(object: T, doesMatch: Pa * console.log(result); // { id: 1, name: 'Alice' } */ export function find>( - object: T, + object: T | null | undefined, doesMatchProperty: [keyof T, unknown] ): T | undefined; @@ -127,7 +130,7 @@ export function find>( * Finds the first item in an object that has a specific property, where the property name is provided as a string. * * @template T - * @param {T extends Record ? T : never} object - The object to search through. + * @param {T extends Record ? T | null | undefined: never} object - The object to search through. * @param {string} propertyToCheck - The property name to check. * @returns {T | undefined} - The first property value that has the specified property, or `undefined` if no match is found. * @@ -137,13 +140,16 @@ export function find>( * const result = find(obj, 'name'); * console.log(result); // { id: 1, name: 'Alice' } */ -export function find>(object: T, propertyToCheck: string): T | undefined; +export function find>( + object: T | null | undefined, + propertyToCheck: string +): T | undefined; /** * Finds the first item in an object that has a specific property, where the property name is provided as a string. * * @template T - * @param {T extends Record ? T : never} object - The object to search through. + * @param {T extends Record ? T | null | undefined: never} object - The object to search through. * @param {readonly T[] | Record} source - The source array or object to search through. * @param {((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string} doesMatch - The criteria to match. It can be a function, a partial object, a key-value pair, or a property name. * @param {string} propertyToCheck - The property name to check. @@ -156,14 +162,13 @@ export function find>(object: T, propertyToChe * console.log(result); // { id: 1, name: 'Alice' } */ export function find( - source: readonly T[] | Record, + source: ArrayLike | Record | null | undefined, doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string ): T | undefined { - let values = source; - - if (!Array.isArray(source)) { - values = Object.values(source); + if (!source) { + return undefined; } + const values = Array.isArray(source) ? source : Object.values(source); switch (typeof doesMatch) { case 'function': { diff --git a/src/compat/array/findIndex.spec.ts b/src/compat/array/findIndex.spec.ts index 1484ca01d..f2eab5053 100644 --- a/src/compat/array/findIndex.spec.ts +++ b/src/compat/array/findIndex.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { findIndex } from './findIndex'; +import { args } from '../_internal/args'; import { slice } from '../_internal/slice'; describe('findIndex', () => { @@ -40,4 +41,24 @@ describe('findIndex', () => { expect(args).toEqual(['a', 0, array]); }); + + it('findIndex should support fromIndex', () => { + expect(findIndex(objects, { b: 2 }, -1)).toBe(2); + expect(findIndex(objects, { b: 2 }, 0)).toBe(2); + expect(findIndex(objects, { b: 2 }, 1)).toBe(2); + expect(findIndex(objects, { b: 2 }, 2)).toBe(2); + expect(findIndex(objects, { b: 2 }, 3)).toBe(-1); + expect(findIndex(objects, { b: 2 }, 4)).toBe(-1); + }); + + it('should return `-1` when provided `null` or `undefined`', () => { + expect(findIndex(null, 'a')).toBe(-1); + expect(findIndex(undefined, 'a')).toBe(-1); + }); + + it('should support array-like objects', () => { + expect(findIndex({ 0: 'a', 1: 'b', length: 2 }, i => i === 'b')).toBe(1); + expect(findIndex('123', i => i === '2')).toBe(1); + expect(findIndex(args, i => i === 2)).toBe(1); + }); }); diff --git a/src/compat/array/findIndex.ts b/src/compat/array/findIndex.ts index 1897eac08..ea962d46f 100644 --- a/src/compat/array/findIndex.ts +++ b/src/compat/array/findIndex.ts @@ -6,8 +6,9 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * Finds the index of the first item in an array that matches the given predicate function. * * @template T - * @param {T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {(item: T, index: number, arr: T[]) => unknown} doesMatch - A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. + * @param {number} [fromIndex=0] - The index to start the search from, defaults to 0. * @returns {number} - The index of the first item that matches the predicate, or `undefined` if no match is found. * * @example @@ -17,16 +18,18 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * console.log(result); // 4 */ export function findIndex( - arr: readonly T[], - doesMatch: (item: T, index: number, arr: readonly T[]) => unknown + arr: ArrayLike | null | undefined, + doesMatch: (item: T, index: number, arr: readonly T[]) => unknown, + fromIndex?: number ): number; /** * Finds the index of the first item in an array that matches the given partial object. * * @template T - * @param {T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {Partial} doesMatch - A partial object that specifies the properties to match. + * @param {number} [fromIndex=0] - The index to start the search from, defaults to 0. * @returns {number} - The index of the first item that matches the partial object, or `undefined` if no match is found. * * @example @@ -35,14 +38,15 @@ export function findIndex( * const result = findIndex(items, { name: 'Bob' }); * console.log(result); // 1 */ -export function findIndex(arr: readonly T[], doesMatch: Partial): number; +export function findIndex(arr: ArrayLike | null | undefined, doesMatch: Partial, fromIndex?: number): number; /** * Finds the index of the first item in an array that matches a property with a specific value. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. + * @param {number} [fromIndex=0] - The index to start the search from, defaults to 0. * @returns {number} - The index of the first item that has the specified property value, or `undefined` if no match is found. * * @example @@ -51,14 +55,19 @@ export function findIndex(arr: readonly T[], doesMatch: Partial): number; * const result = findIndex(items, ['name', 'Alice']); * console.log(result); // 0 */ -export function findIndex(arr: readonly T[], doesMatchProperty: [keyof T, unknown]): number; +export function findIndex( + arr: ArrayLike | null | undefined, + doesMatchProperty: [keyof T, unknown], + fromIndex?: number +): number; /** * Finds the index of the first item in an array that has a specific property, where the property name is provided as a string. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {string} propertyToCheck - The property name to check. + * @param {number} [fromIndex=0] - The index to start the search from, defaults to 0. * @returns {number} - The index of the first item that has the specified property, or `undefined` if no match is found. * * @example @@ -67,16 +76,16 @@ export function findIndex(arr: readonly T[], doesMatchProperty: [keyof T, unk * const result = findIndex(items, 'name'); * console.log(result); // 0 */ -export function findIndex(arr: readonly T[], propertyToCheck: string): number; +export function findIndex(arr: ArrayLike | null | undefined, propertyToCheck: string, fromIndex?: number): number; /** * Finds the index of the first item in an array that has a specific property, where the property name is provided as a string. * * @template T - * @param {readonly T[]} arr - The array to search through. - * @param {readonly T[]} source - The source array to search for the matching item. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string} doesMatch - The criteria to match against the items in the array. This can be a function, a partial object, a key-value pair, or a property name. * @param {string} propertyToCheck - The property name to check for in the items of the array. + * @param {number} [fromIndex=0] - The index to start the search from, defaults to 0. * @returns {number} - The index of the first item that has the specified property, or `undefined` if no match is found. * * @example @@ -86,25 +95,37 @@ export function findIndex(arr: readonly T[], propertyToCheck: string): number * console.log(result); // 0 */ export function findIndex( - source: readonly T[], - doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string + arr: ArrayLike | null | undefined, + doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string, + fromIndex: number = 0 ): number { + if (!arr) { + return -1; + } + if (fromIndex < 0) { + fromIndex = Math.max(arr.length + fromIndex, 0); + } + const subArray = Array.from(arr).slice(fromIndex); + let index = -1; switch (typeof doesMatch) { case 'function': { - return source.findIndex(doesMatch); + index = subArray.findIndex(doesMatch); + break; } case 'object': { if (Array.isArray(doesMatch) && doesMatch.length === 2) { const key = doesMatch[0]; const value = doesMatch[1]; - return source.findIndex(matchesProperty(key, value)); + index = subArray.findIndex(matchesProperty(key, value)); } else { - return source.findIndex(matches(doesMatch)); + index = subArray.findIndex(matches(doesMatch)); } + break; } case 'string': { - return source.findIndex(property(doesMatch)); + index = subArray.findIndex(property(doesMatch)); } } + return index === -1 ? -1 : index + fromIndex; } diff --git a/src/compat/array/findLastIndex.spec.ts b/src/compat/array/findLastIndex.spec.ts index 731d23766..856997264 100644 --- a/src/compat/array/findLastIndex.spec.ts +++ b/src/compat/array/findLastIndex.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { findLastIndex } from './findLastIndex'; +import { args } from '../_internal/args'; import { falsey } from '../_internal/falsey'; import { slice } from '../_internal/slice'; import { stubZero } from '../_internal/stubZero'; @@ -96,4 +97,15 @@ describe('findLastIndex', () => { it(`\`findLastIndex\` should coerce \`fromIndex\` to an integer`, () => { expect(findLastIndex(array, x => x === 2, 4.2)).toBe(4); }); + + it('should return `-1` when provided `null` or `undefined`', () => { + expect(findLastIndex(null, 'a')).toBe(-1); + expect(findLastIndex(undefined, 'a')).toBe(-1); + }); + + it('should support array-like objects', () => { + expect(findLastIndex({ 0: 'a', 1: 'b', length: 2 }, i => i === 'b')).toBe(1); + expect(findLastIndex('123', i => i === '2')).toBe(1); + expect(findLastIndex(args, i => i === 2)).toBe(1); + }); }); diff --git a/src/compat/array/findLastIndex.ts b/src/compat/array/findLastIndex.ts index 4b4931117..dcb4d42df 100644 --- a/src/compat/array/findLastIndex.ts +++ b/src/compat/array/findLastIndex.ts @@ -6,7 +6,7 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * Iterates through an array in reverse order and returns the index of the first item that matches the given predicate function. * * @template T - * @param {T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {(item: T, index: number, arr: T[]) => unknown} doesMatch - A function that takes an item, its index, and the array, and returns a truthy value if the item matches the criteria. * @param {number} [fromIndex=arr.length - 1] - The index to start the search from, defaults to the last index of the array. * @returns {number} - The index of the first item that matches the predicate, or `undefined` if no match is found. @@ -18,7 +18,7 @@ import { matchesProperty } from '../predicate/matchesProperty.ts'; * console.log(result); // 4 */ export function findLastIndex( - arr: readonly T[], + arr: ArrayLike | null | undefined, doesMatch: (item: T, index: number, arr: readonly T[]) => unknown, fromIndex?: number ): number; @@ -27,7 +27,7 @@ export function findLastIndex( * Finds the index of the first item in an array that matches the given partial object. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {Partial} doesMatch - A partial object that specifies the properties to match. * @param {number} [fromIndex=arr.length - 1] - The index to start the search from, defaults to the last index of the array. * @returns {number} - The index of the first item that matches the partial object, or `undefined` if no match is found. @@ -38,13 +38,17 @@ export function findLastIndex( * const result = findLastIndex(items, { name: 'Bob' }); * console.log(result); // 1 */ -export function findLastIndex(arr: readonly T[], doesMatch: Partial, fromIndex?: number): number; +export function findLastIndex( + arr: ArrayLike | null | undefined, + doesMatch: Partial, + fromIndex?: number +): number; /** * Finds the index of the first item in an array that matches a property with a specific value. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {[keyof T, unknown]} doesMatchProperty - An array where the first element is the property key and the second element is the value to match. * @param {number} [fromIndex=arr.length - 1] - The index to start the search from, defaults to the last index of the array. * @returns {number} - The index of the first item that has the specified property value, or `undefined` if no match is found. @@ -55,13 +59,17 @@ export function findLastIndex(arr: readonly T[], doesMatch: Partial, fromI * const result = findLastIndex(items, ['name', 'Alice']); * console.log(result); // 0 */ -export function findLastIndex(arr: readonly T[], doesMatchProperty: [keyof T, unknown], fromIndex?: number): number; +export function findLastIndex( + arr: ArrayLike | null | undefined, + doesMatchProperty: [keyof T, unknown], + fromIndex?: number +): number; /** * Finds the index of the first item in an array that has a specific property, where the property name is provided as a string. * * @template T - * @param {readonly T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {string} propertyToCheck - The property name to check. * @param {number} [fromIndex=arr.length - 1] - The index to start the search from, defaults to the last index of the array. * @returns {number} - The index of the first item that has the specified property, or `undefined` if no match is found. @@ -72,13 +80,17 @@ export function findLastIndex(arr: readonly T[], doesMatchProperty: [keyof T, * const result = findLastIndex(items, 'name'); * console.log(result); // 1 */ -export function findLastIndex(arr: readonly T[], propertyToCheck: string, fromIndex?: number): number; +export function findLastIndex( + arr: ArrayLike | null | undefined, + propertyToCheck: string, + fromIndex?: number +): number; /** * Finds the index of the first item in an array that has a specific property, where the property name is provided as a string. * * @template T - * @param {T[]} arr - The array to search through. + * @param {ArrayLike | null | undefined} arr - The array to search through. * @param {((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string} doesMatch - The property name to check. * @param {number} [fromIndex=arr.length - 1] - The index to start the search from, defaults to the last index of the array. * @returns {number} - The index of the first item that has the specified property, or `undefined` if no match is found. @@ -90,34 +102,37 @@ export function findLastIndex(arr: readonly T[], propertyToCheck: string, fro * console.log(result); // 1 */ export function findLastIndex( - arr: readonly T[], + arr: ArrayLike | null | undefined, doesMatch: ((item: T, index: number, arr: any) => unknown) | Partial | [keyof T, unknown] | string, - fromIndex: number = arr.length - 1 + fromIndex: number = arr ? arr.length - 1 : 0 ): number { + if (!arr) { + return -1; + } if (fromIndex < 0) { fromIndex = Math.max(arr.length + fromIndex, 0); } else { fromIndex = Math.min(fromIndex, arr.length - 1); } - arr = arr.slice(0, fromIndex + 1); + const subArray = Array.from(arr).slice(0, fromIndex + 1); switch (typeof doesMatch) { case 'function': { - return arr.findLastIndex(doesMatch); + return subArray.findLastIndex(doesMatch); } case 'object': { if (Array.isArray(doesMatch) && doesMatch.length === 2) { const key = doesMatch[0]; const value = doesMatch[1]; - return arr.findLastIndex(matchesProperty(key, value)); + return subArray.findLastIndex(matchesProperty(key, value)); } else { - return arr.findLastIndex(matches(doesMatch)); + return subArray.findLastIndex(matches(doesMatch)); } } case 'string': { - return arr.findLastIndex(property(doesMatch)); + return subArray.findLastIndex(property(doesMatch)); } } } diff --git a/src/compat/array/indexOf.spec.ts b/src/compat/array/indexOf.spec.ts index ec0abc90e..dd68e621d 100644 --- a/src/compat/array/indexOf.spec.ts +++ b/src/compat/array/indexOf.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { indexOf } from './indexOf'; +import { args } from '../_internal/args'; import { falsey } from '../_internal/falsey'; import { stubZero } from '../_internal/stubZero'; @@ -69,4 +70,19 @@ describe('indexOf', () => { it('should coerce `fromIndex` to an integer', () => { expect(indexOf(array, 2, 1.2)).toBe(1); }); + + it('should return `-1` when provided `null` or `undefined`', () => { + expect(indexOf(null as any, 1)).toBe(-1); + expect(indexOf(undefined as any, 1)).toBe(-1); + }); + + it('should return `-1` when provided none array-like object', () => { + expect(indexOf(1 as any, 1)).toBe(-1); + }); + + it('should support array-like', () => { + expect(indexOf({ 0: 1, 1: 2, length: 2 }, 2)).toBe(1); + expect(indexOf('123', '2')).toBe(1); + expect(indexOf(args, 2)).toBe(1); + }); }); diff --git a/src/compat/array/indexOf.ts b/src/compat/array/indexOf.ts index cc98a1e4a..1710ce353 100644 --- a/src/compat/array/indexOf.ts +++ b/src/compat/array/indexOf.ts @@ -1,3 +1,5 @@ +import { isArrayLike } from '../predicate/isArrayLike.ts'; + /** * Finds the index of the first occurrence of a value in an array. * @@ -5,8 +7,7 @@ * It uses strict equality (`===`) to compare elements. * * @template T - The type of elements in the array. - * @template U - The type of the value to search for. - * @param {T[] | null | undefined} array - The array to search. + * @param {ArrayLike | null | undefined} array - The array to search. * @param {T} searchElement - The value to search for. * @param {number} [fromIndex] - The index to start the search at. * @returns {number} The index (zero-based) of the first occurrence of the value in the array, or `-1` if the value is not found. @@ -16,8 +17,8 @@ * indexOf(array, 3); // => 2 * indexOf(array, NaN); // => 3 */ -export function indexOf(array: readonly T[] | null | undefined, searchElement: T, fromIndex?: number): number { - if (array == null) { +export function indexOf(array: ArrayLike | null | undefined, searchElement: T, fromIndex?: number): number { + if (!isArrayLike(array)) { return -1; } @@ -40,5 +41,5 @@ export function indexOf(array: readonly T[] | null | undefined, searchElement // Array.prototype.indexOf already handles `fromIndex < -array.length`, `fromIndex >= array.length` and converts `fromIndex` to an integer, so we don't need to handle those cases here. // And it uses strict equality (===) to compare elements like `lodash/indexOf` does. - return array.indexOf(searchElement as T, fromIndex); + return Array.from(array).indexOf(searchElement, fromIndex); } diff --git a/src/compat/array/intersection.ts b/src/compat/array/intersection.ts index 93f81e105..1fb9c3106 100644 --- a/src/compat/array/intersection.ts +++ b/src/compat/array/intersection.ts @@ -2,6 +2,23 @@ import { intersection as intersectionToolkit } from '../../array/intersection.ts import { uniq } from '../../array/uniq.ts'; import { isArrayLikeObject } from '../predicate/isArrayLikeObject.ts'; +/** + * Returns the intersection of multiple arrays. + * + * This function takes multiple arrays and returns a new array containing the elements that are + * present in all provided arrays. It effectively filters out any elements that are not found + * in every array. + * + * @template T - The type of elements in the arrays. + * @param {...(ArrayLike | null | undefined)} arrays - The arrays to compare. + * @returns {T[]} A new array containing the elements that are present in all arrays. + * + * @example + * const array1 = [1, 2, 3, 4, 5]; + * const array2 = [3, 4, 5, 6, 7]; + * const result = intersection(array1, array2); + * // result will be [3, 4, 5] since these elements are in both arrays. + */ export function intersection(...arrays: Array | null | undefined>): T[] { if (arrays.length === 0) { return []; diff --git a/src/compat/array/join.spec.ts b/src/compat/array/join.spec.ts index 27a187ef8..e75343841 100644 --- a/src/compat/array/join.spec.ts +++ b/src/compat/array/join.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { join } from './join.js'; +import { args } from '../_internal/args'; describe('join', () => { it('should join elements of an array into a string', () => { @@ -13,4 +14,16 @@ describe('join', () => { const result = join(arr, '~'); expect(result).toBe('a~b~c'); }); + + it('should return an empty string for non-array-like values', () => { + expect(join(null)).toBe(''); + expect(join(undefined)).toBe(''); + expect(join(1 as any)).toBe(''); + }); + + it('should support array-like', () => { + expect(join({ 0: 1, 1: 2, length: 2 })).toBe('1,2'); + expect(join('123')).toBe('1,2,3'); + expect(join(args)).toBe('1,2,3'); + }); }); diff --git a/src/compat/array/join.ts b/src/compat/array/join.ts index 1cb698247..1ed69e2a6 100644 --- a/src/compat/array/join.ts +++ b/src/compat/array/join.ts @@ -1,8 +1,10 @@ +import { isArrayLike } from '../predicate/isArrayLike.ts'; + /** * Joins elements of an array into a string. * * @template T - The type of elements in the array. - * @param {T[]} array - The array to join. + * @param {ArrayLike | null | undefined} array - The array to join. * @param {string} separator - The separator used to join the elements, default is common separator `,`. * @returns {string} - Returns a string containing all elements of the array joined by the specified separator. * @@ -11,6 +13,9 @@ * const result = join(arr, "~"); * console.log(result); // Output: "a~b~c" */ -export function join(array: readonly T[], separator = ','): string { - return array.join(separator); +export function join(array: ArrayLike | null | undefined, separator = ','): string { + if (!isArrayLike(array)) { + return ''; + } + return Array.from(array).join(separator); } diff --git a/src/compat/object/pick.spec.ts b/src/compat/object/pick.spec.ts index 86fc44768..e43082c9d 100644 --- a/src/compat/object/pick.spec.ts +++ b/src/compat/object/pick.spec.ts @@ -67,4 +67,11 @@ describe('compat/pick', () => { array2[1] = 2; expect(pick({ array: [1, 2, 3] }, 'array[1]')).toEqual({ array: array2 }); }); + + it('should not pick from nonexistent keys', () => { + const obj: { a?: unknown; b?: unknown } = {}; + const result = pick(obj, ['a', 'b']); + + expect(Reflect.ownKeys(result)).toEqual([]); + }); }); diff --git a/src/compat/object/pick.ts b/src/compat/object/pick.ts index 6c609abb6..bbeaf881c 100644 --- a/src/compat/object/pick.ts +++ b/src/compat/object/pick.ts @@ -1,4 +1,5 @@ import { get } from './get.ts'; +import { has } from './has.ts'; import { set } from './set.ts'; import { isNil } from '../predicate/isNil.ts'; @@ -116,6 +117,10 @@ export function pick< for (const key of keys) { const value = get(obj, key); + if (value === undefined && !has(obj, key)) { + continue; + } + if (typeof key === 'string' && Object.hasOwn(obj, key)) { result[key] = value; } else { diff --git a/src/compat/string/escape.ts b/src/compat/string/escape.ts index 94355d5dd..639674474 100644 --- a/src/compat/string/escape.ts +++ b/src/compat/string/escape.ts @@ -1,6 +1,19 @@ import { escape as escapeToolkit } from '../../string/escape.ts'; import { toString } from '../util/toString.ts'; +/** + * Converts the characters "&", "<", ">", '"', and "'" in `str` to their corresponding HTML entities. + * For example, "<" becomes "<". + * + * @param {string} str The string to escape. + * @returns {string} Returns the escaped string. + * + * @example + * escape('This is a
element.'); // returns 'This is a <div> element.' + * escape('This is a "quote"'); // returns 'This is a "quote"' + * escape("This is a 'quote'"); // returns 'This is a 'quote'' + * escape('This is a & symbol'); // returns 'This is a & symbol' + */ export function escape(string?: string): string { return escapeToolkit(toString(string)); } diff --git a/src/object/cloneDeep.spec.ts b/src/object/cloneDeep.spec.ts index 02f80041b..1e282eebc 100644 --- a/src/object/cloneDeep.spec.ts +++ b/src/object/cloneDeep.spec.ts @@ -46,7 +46,7 @@ describe('cloneDeep', () => { // object //------------------------------------------------------------------------------------- it('should clone objects', () => { - const obj = { a: 1, b: 'es-toolkit', c: [1, 2, 3] }; + const obj = { a: 1, b: 'es-toolkit', c: [1, 2, 3], [Symbol()]: 2 }; const clonedObj = cloneDeep(obj); expect(clonedObj).toEqual(obj); @@ -347,4 +347,32 @@ describe('cloneDeep', () => { expect(cloned).not.toBe(buffer); expect(cloned).toEqual(buffer); }); + + it('should clone read-only properties', () => { + const object: any = {}; + + Object.defineProperties(object, { + first: { + enumerable: true, + writable: true, + value: 1, + }, + second: { + enumerable: true, + get() { + return 2; + }, + }, + }); + + object.third = 3; + + const cloned = cloneDeep(object); + expect(cloned).not.toBe(object); + expect(cloned).toEqual({ + first: 1, + second: 2, + third: 3, + }); + }); }); diff --git a/src/object/cloneDeep.ts b/src/object/cloneDeep.ts index 4bd71c046..811dc6306 100644 --- a/src/object/cloneDeep.ts +++ b/src/object/cloneDeep.ts @@ -1,3 +1,4 @@ +import { getSymbols } from '../compat/_internal/getSymbols.ts'; import { isPrimitive } from '../predicate/isPrimitive.ts'; import { isTypedArray } from '../predicate/isTypedArray.ts'; @@ -196,13 +197,13 @@ function cloneDeepImpl(obj: T, stack = new Map()): T { // eslint-disable-next-line export function copyProperties(target: any, source: any, stack?: Map): void { - const keys = Object.keys(source); + const keys = [...Object.keys(source), ...getSymbols(source)]; for (let i = 0; i < keys.length; i++) { const key = keys[i]; - const descriptor = Object.getOwnPropertyDescriptor(source, key); + const descriptor = Object.getOwnPropertyDescriptor(target, key); - if (descriptor?.writable || descriptor?.set) { + if (descriptor == null || descriptor.writable) { target[key] = cloneDeepImpl(source[key], stack); } }