diff --git a/benchmarks/performance/defer.bench.ts b/benchmarks/performance/defer.bench.ts new file mode 100644 index 000000000..ffe1015c5 --- /dev/null +++ b/benchmarks/performance/defer.bench.ts @@ -0,0 +1,15 @@ +import { bench, describe } from 'vitest'; +import { defer as deferToolkit } from 'es-toolkit/compat'; +import { defer as deferLodash } from 'lodash'; + +describe('defer', () => { + bench('es-toolkit/defer', () => { + const id = deferToolkit(() => {}); + clearTimeout(id); + }); + + bench('lodash/defer', () => { + const id = deferLodash(() => {}); + clearTimeout(id); + }); +}); diff --git a/benchmarks/performance/isFinite.bench.ts b/benchmarks/performance/isFinite.bench.ts new file mode 100644 index 000000000..69aa21f93 --- /dev/null +++ b/benchmarks/performance/isFinite.bench.ts @@ -0,0 +1,25 @@ +import { isFinite as isFiniteToolkit } from 'es-toolkit/compat'; +import { isFinite as isFiniteLodash } from 'lodash'; +import { bench, describe } from 'vitest'; + +describe('isFinite', () => { + bench('es-toolkit/isFinite', () => { + isFiniteToolkit(1); + isFiniteToolkit(1.12); + isFiniteToolkit(Infinity); + isFiniteToolkit(-Infinity); + isFiniteToolkit([]); + isFiniteToolkit({}); + isFiniteToolkit('1'); + }); + + bench('lodash/isFinite', () => { + isFiniteLodash(1); + isFiniteLodash(1.12); + isFiniteLodash(Infinity); + isFiniteLodash(-Infinity); + isFiniteLodash([]); + isFiniteLodash({}); + isFiniteLodash('1'); + }); +}); diff --git a/benchmarks/performance/pad.bench.ts b/benchmarks/performance/pad.bench.ts index 68503248a..b90bdbd2a 100644 --- a/benchmarks/performance/pad.bench.ts +++ b/benchmarks/performance/pad.bench.ts @@ -1,15 +1,21 @@ import { bench, describe } from 'vitest'; -import { pad as padStartToolkit } from 'es-toolkit'; -import { pad as padStartLodash } from 'lodash'; +import { pad as padToolkit } from 'es-toolkit'; +import { pad as padToolkitCompat } from 'es-toolkit/compat'; +import { pad as padLodash } from 'lodash'; describe('pad', () => { bench('es-toolkit/pad', () => { const str = 'abc'; - padStartToolkit(str, 6, '_-'); + padToolkit(str, 6, '_-'); + }); + + bench('es-toolkit/compat/pad', () => { + const str = 'abc'; + padToolkitCompat(str, 6, '_-'); }); bench('lodash/pad', () => { const str = 'abc'; - padStartLodash(str, 6, '_-'); + padLodash(str, 6, '_-'); }); }); diff --git a/benchmarks/performance/padEnd.bench.ts b/benchmarks/performance/padEnd.bench.ts index 5c056a0e0..ddc7801ca 100644 --- a/benchmarks/performance/padEnd.bench.ts +++ b/benchmarks/performance/padEnd.bench.ts @@ -1,15 +1,15 @@ import { bench, describe } from 'vitest'; -import { padEnd as padStartToolkit } from 'es-toolkit/compat'; -import { padEnd as padStartLodash } from 'lodash'; +import { padEnd as padEndToolkit } from 'es-toolkit/compat'; +import { padEnd as padEndLodash } from 'lodash'; describe('padEnd', () => { bench('es-toolkit/padEnd', () => { const str = 'abc'; - padStartToolkit(str, 6, '_-'); + padEndToolkit(str, 6, '_-'); }); bench('lodash/padEnd', () => { const str = 'abc'; - padStartLodash(str, 6, '_-'); + padEndLodash(str, 6, '_-'); }); }); diff --git a/benchmarks/performance/toFinite.bench.ts b/benchmarks/performance/toFinite.bench.ts new file mode 100644 index 000000000..f9e85f084 --- /dev/null +++ b/benchmarks/performance/toFinite.bench.ts @@ -0,0 +1,25 @@ +import { bench, describe } from 'vitest'; +import { toFinite as toFiniteToolkitCompat } from 'es-toolkit/compat'; +import { toFinite as toFiniteLodash } from 'lodash'; + +describe('toFinite', () => { + bench('es-toolkit/compat/toFinite', () => { + toFiniteToolkitCompat({ valueOf: () => 1 }); + toFiniteToolkitCompat({ valueOf: () => 2 }); + toFiniteToolkitCompat({ toString: () => '3' }); + toFiniteToolkitCompat('0b101010'); + toFiniteToolkitCompat('0o12345'); + toFiniteToolkitCompat('0x1a2b3c'); + toFiniteToolkitCompat('1.1'); + }); + + bench('lodash/toFinite', () => { + toFiniteLodash({ valueof: () => 1 }); + toFiniteLodash({ valueof: () => 2 }); + toFiniteLodash({ toString: () => '3' }); + toFiniteLodash('0b101010'); + toFiniteLodash('0o12345'); + toFiniteLodash('0x1a2b3c'); + toFiniteLodash('1.1'); + }); +}); diff --git a/benchmarks/performance/toInteger.bench.ts b/benchmarks/performance/toInteger.bench.ts new file mode 100644 index 000000000..cd8e824e4 --- /dev/null +++ b/benchmarks/performance/toInteger.bench.ts @@ -0,0 +1,25 @@ +import { bench, describe } from 'vitest'; +import { toInteger as toIntegerToolkitCompat } from 'es-toolkit/compat'; +import { toInteger as toIntegerLodash } from 'lodash'; + +describe('toInteger', () => { + bench('es-toolkit/compat/toInteger', () => { + toIntegerToolkitCompat({ valueOf: () => 1 }); + toIntegerToolkitCompat({ valueOf: () => 2 }); + toIntegerToolkitCompat({ toString: () => '3' }); + toIntegerToolkitCompat('0b101010'); + toIntegerToolkitCompat('0o12345'); + toIntegerToolkitCompat('0x1a2b3c'); + toIntegerToolkitCompat('1.1'); + }); + + bench('lodash/toInteger', () => { + toIntegerLodash({ valueof: () => 1 }); + toIntegerLodash({ valueof: () => 2 }); + toIntegerLodash({ toString: () => '3' }); + toIntegerLodash('0b101010'); + toIntegerLodash('0o12345'); + toIntegerLodash('0x1a2b3c'); + toIntegerLodash('1.1'); + }); +}); diff --git a/benchmarks/performance/toNumber.bench.ts b/benchmarks/performance/toNumber.bench.ts new file mode 100644 index 000000000..3001da48c --- /dev/null +++ b/benchmarks/performance/toNumber.bench.ts @@ -0,0 +1,25 @@ +import { bench, describe } from 'vitest'; +import { toNumber as toNumberToolkitCompat } from 'es-toolkit/compat'; +import { toNumber as toNumberLodash } from 'lodash'; + +describe('toNumber', () => { + bench('es-toolkit/compat/toNumber', () => { + toNumberToolkitCompat({ valueOf: () => 1 }); + toNumberToolkitCompat({ valueOf: () => 2 }); + toNumberToolkitCompat({ toString: () => '3' }); + toNumberToolkitCompat('0b101010'); + toNumberToolkitCompat('0o12345'); + toNumberToolkitCompat('0x1a2b3c'); + toNumberToolkitCompat('1.1'); + }); + + bench('lodash/toNumber', () => { + toNumberLodash({ valueof: () => 1 }); + toNumberLodash({ valueof: () => 2 }); + toNumberLodash({ toString: () => '3' }); + toNumberLodash('0b101010'); + toNumberLodash('0o12345'); + toNumberLodash('0x1a2b3c'); + toNumberLodash('1.1'); + }); +}); diff --git a/docs/ja/reference/array/compact.md b/docs/ja/reference/array/compact.md index 61d006b73..2ac6d0171 100644 --- a/docs/ja/reference/array/compact.md +++ b/docs/ja/reference/array/compact.md @@ -1,11 +1,11 @@ # compact -偽と評価される値である `false`、`null`、`0`、`''`、`undefined`、`NaN` を除去した新しい配列を返します。 +偽と評価される値である `false`、 `null`、 `0`、 `0n`, `''`、 `undefined`、 `NaN` を除去した新しい配列を返します。 ## インターフェース ```typescript -function compact(arr: T[]): Array>; +function compact(arr: T[]): Array>; ``` ### パラメータ @@ -14,11 +14,11 @@ function compact(arr: T[]): Array>`): 偽と評価される値をすべて除去した新しい配列。 +(`Array>`): 偽と評価される値をすべて除去した新しい配列。 ## 例 ```typescript -compact([0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); +compact([0, 0n, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); // 戻り値: [1, 2, 3, 4, 5] ``` diff --git a/docs/ja/reference/array/maxBy.md b/docs/ja/reference/array/maxBy.md index fac903534..8024b2ab3 100644 --- a/docs/ja/reference/array/maxBy.md +++ b/docs/ja/reference/array/maxBy.md @@ -24,4 +24,12 @@ function maxBy(items: T[], getValue: (element: T) => number): T; ```typescript maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 戻り値: { a: 3 } maxBy([], x => x.a); // 戻り値: undefined +maxBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // 戻り値: { name: 'john', age: 30 } ``` diff --git a/docs/ja/reference/array/minBy.md b/docs/ja/reference/array/minBy.md index e19f25c9b..ebc43e18a 100644 --- a/docs/ja/reference/array/minBy.md +++ b/docs/ja/reference/array/minBy.md @@ -22,4 +22,12 @@ function minBy(items: T[], getValue: (element: T) => number): T; ```typescript minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 戻り値: { a: 1 } minBy([], x => x.a); // 戻り値: undefined +minBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // 戻り値: { name: 'joe', age: 26 } ``` diff --git a/docs/ja/reference/compat/predicate/isFinite.md b/docs/ja/reference/compat/predicate/isFinite.md new file mode 100644 index 000000000..aca7ff67f --- /dev/null +++ b/docs/ja/reference/compat/predicate/isFinite.md @@ -0,0 +1,37 @@ +# isFinite + +::: info +この関数は互換性のために `es-toolkit/compat` からのみインポートできます。代替となるネイティブ JavaScript API が存在するか、まだ十分に最適化されていないためです。 + +`es-toolkit/compat` からこの関数をインポートすると、[lodash と完全に同じように動作](../../../compatibility.md)します。 +::: + +与えられた値が有限の数値かどうかを確認します。 + +この関数は、TypeScriptにおいて型を判別するための述語関数としても使用でき、引数の型を `number` に絞り込むことができます。 + +## インターフェース + +```typescript +function isFinite(value: unknown): value is number; +``` + +### パラメータ + +- `value` (`unknown`): 有限の数値かどうかを確認する値。 + +### 戻り値 + +(`value is number`): 値が有限の数値であれば `true`、そうでなければ `false` を返します。 + +## 例 + +```typescript +const value1 = 100; +const value2 = Infinity; +const value3 = '100'; + +console.log(isFinite(value1)); // true +console.log(isFinite(value2)); // false +console.log(isFinite(value3)); // false +``` diff --git a/docs/ja/reference/function/curry.md b/docs/ja/reference/function/curry.md index 329fd9acc..1369fed24 100644 --- a/docs/ja/reference/function/curry.md +++ b/docs/ja/reference/function/curry.md @@ -17,7 +17,6 @@ function curry( func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R ): (p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => (p5: P5) => R; function curry(func: (...args: any[]) => any): (...args: any[]) => any; -function curry(func: (...args: any[]) => any): (...args: any[]) => any; ``` ### パラメータ diff --git a/docs/ja/reference/math/maxBy.md b/docs/ja/reference/math/maxBy.md deleted file mode 100644 index 9baf1f2a2..000000000 --- a/docs/ja/reference/math/maxBy.md +++ /dev/null @@ -1,27 +0,0 @@ -# maxBy - -与えられた配列内の要素の中から、条件に従って最大値を持つ最初の要素を選択する関数です。 - -配列が空でない場合、条件に従って最大値を持つ最初の要素を返し、空の場合は `undefined` を返します。 - -## インターフェース - -```typescript -function maxBy(elements: T[], selector: (element: T) => number): T; -``` - -### パラメータ - -- `elements`: 検索する要素の配列。 -- `selector`: 要素を受け取り、オブジェクトのプロパティを返す関数。 - -### 戻り値 - -関数の最大値を持つ配列の最初の要素。配列が空の場合は `undefined` を返します。 - -### 例 - -```typescript -maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 結果: { a: 3 } -maxBy([], x => x.a); // 結果: undefined -``` diff --git a/docs/ja/reference/math/minBy.md b/docs/ja/reference/math/minBy.md deleted file mode 100644 index acb3bebe4..000000000 --- a/docs/ja/reference/math/minBy.md +++ /dev/null @@ -1,27 +0,0 @@ -# minBy - -与えられた配列内の要素の中から、条件に従って最小値を持つ最初の要素を選択する関数です。 - -配列が空でない場合、条件に従って最小値を持つ最初の要素を返し、空の場合は `undefined` を返します。 - -## インターフェース - -```typescript -function minBy(elements: T[], selector: (element: T) => number): T; -``` - -### パラメータ - -- `elements`: 検索する要素の配列。 -- `selector`: 要素を受け取り、オブジェクトのプロパティを返す関数。 - -### 戻り値 - -関数の最小値を持つ配列の最初の要素。配列が空の場合は `undefined` を返します。 - -### 例 - -```typescript -minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 結果: { a: 1 } -minBy([], x => x.a); // 結果: undefined -``` diff --git a/docs/ja/reference/string/startCase.md b/docs/ja/reference/string/startCase.md index 84b9d41e6..37d6c0107 100644 --- a/docs/ja/reference/string/startCase.md +++ b/docs/ja/reference/string/startCase.md @@ -25,8 +25,8 @@ import { startCase } from 'es-toolkit/string'; startCase('--foo-bar--'); // 'Foo Bar' を返します startCase('fooBar'); // 'Foo Bar' を返します -startCase('__FOO_BAR__'); // 'FOO BAR' を返します -startCase('XMLHttpRequest'); // 'XML Http Request' を返します +startCase('__FOO_BAR__'); // 'Foo Bar' を返します +startCase('XMLHttpRequest'); // 'Xml Http Request' を返します startCase('_abc_123_def'); // 'Abc 123 Def' を返します startCase('__abc__123__def__'); // 'Abc 123 Def' を返します startCase('_-_-_-_'); // '' を返します diff --git a/docs/ko/reference/array/compact.md b/docs/ko/reference/array/compact.md index 56d7ec98f..38ac21fea 100644 --- a/docs/ko/reference/array/compact.md +++ b/docs/ko/reference/array/compact.md @@ -1,11 +1,11 @@ # compact -거짓으로 평가될 수 있는 값인 `false`, `null`, `0`, `''`, `undefined`, `NaN`을 제거한 새로운 배열을 반환해요. +거짓으로 평가될 수 있는 값인 `false`, `null`, `0`, `0n`, `''`, `undefined`, `NaN`을 제거한 새로운 배열을 반환해요. ## 인터페이스 ```typescript -function compact(arr: T[]): Array>; +function compact(arr: T[]): Array>; ``` ### 파라미터 @@ -14,11 +14,11 @@ function compact(arr: T[]): Array>`): 거짓으로 평가될 수 있는 값을 모두 제거한 새로운 배열. +(`Array>`): 거짓으로 평가될 수 있는 값을 모두 제거한 새로운 배열. ## 예시 ```typescript -compact([0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); +compact([0, 0n, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); // 반환 값: [1, 2, 3, 4, 5] ``` diff --git a/docs/ko/reference/array/maxBy.md b/docs/ko/reference/array/maxBy.md index 81cb0ac89..315f9e211 100644 --- a/docs/ko/reference/array/maxBy.md +++ b/docs/ko/reference/array/maxBy.md @@ -25,4 +25,12 @@ function maxBy(items: T[], getValue: (element: T) => number): T | undefined; ```typescript maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } maxBy([], x => x.a); // Returns: undefined +maxBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // Returns: { name: 'john', age: 30 } ``` diff --git a/docs/ko/reference/array/minBy.md b/docs/ko/reference/array/minBy.md index 232f64df7..c0b54e38b 100644 --- a/docs/ko/reference/array/minBy.md +++ b/docs/ko/reference/array/minBy.md @@ -23,4 +23,12 @@ function minBy(items: T[], getValue: (element: T) => number): T | undefined; ```typescript minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } minBy([], x => x.a); // Returns: undefined +minBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // Returns: { name: 'joe', age: 26 } ``` diff --git a/docs/ko/reference/compat/predicate/isFinite.md b/docs/ko/reference/compat/predicate/isFinite.md new file mode 100644 index 000000000..450026fd7 --- /dev/null +++ b/docs/ko/reference/compat/predicate/isFinite.md @@ -0,0 +1,37 @@ +# isFinite + +::: info +이 함수는 호환성을 위한 `es-toolkit/compat` 에서만 가져올 수 있어요. 대체할 수 있는 네이티브 JavaScript API가 있거나, 아직 충분히 최적화되지 않았기 때문이에요. + +`es-toolkit/compat`에서 이 함수를 가져오면, [lodash와 완전히 똑같이 동작](../../../compatibility.md)해요. +::: + +주어진 값이 유한한 숫자인지 확인해요. + +TypeScript의 타입 가드로 사용할 수 있어요. 파라미터로 주어진 값의 타입을 `number`로 좁혀요. + +## 인터페이스 + +```typescript +function isFinite(value: unknown): value is number; +``` + +### 파라미터 + +- `value`(`unknown`): 유한한 숫자인지 검사할 값. + +### 반환 값 + +(`value is number`): 값이 유한한 숫자일 경우 `true`, 아니면 `false`. + +## 예시 + +```typescript +const value1 = 100; +const value2 = Infinity; +const value3 = '100'; + +console.log(isFinite(value1)); // true +console.log(isFinite(value2)); // false +console.log(isFinite(value3)); // false +``` diff --git a/docs/ko/reference/string/startCase.md b/docs/ko/reference/string/startCase.md index 1c7298ca5..cf90f9964 100644 --- a/docs/ko/reference/string/startCase.md +++ b/docs/ko/reference/string/startCase.md @@ -25,8 +25,8 @@ import { startCase } from 'es-toolkit/string'; startCase('--foo-bar--'); // returns 'Foo Bar' startCase('fooBar'); // returns 'Foo Bar' -startCase('__FOO_BAR__'); // returns 'FOO BAR' -startCase('XMLHttpRequest'); // returns 'XML Http Request' +startCase('__FOO_BAR__'); // returns 'Foo Bar' +startCase('XMLHttpRequest'); // returns 'Xml Http Request' startCase('_abc_123_def'); // returns 'Abc 123 Def' startCase('__abc__123__def__'); // returns 'Abc 123 Def' startCase('_-_-_-_'); // returns '' diff --git a/docs/reference/array/compact.md b/docs/reference/array/compact.md index cdbbd96e7..872d112a6 100644 --- a/docs/reference/array/compact.md +++ b/docs/reference/array/compact.md @@ -1,11 +1,11 @@ # compact -Removes falsey values (`false`, `null`, `0`, `''`, `undefined`, `NaN`) from an array. +Removes falsey values (`false`, `null`, `0`, `0n`, `''`, `undefined`, `NaN`) from an array. ## Signature ```typescript -function compact(arr: T[]): Array>; +function compact(arr: T[]): Array>; ``` ### Parameters @@ -14,11 +14,11 @@ function compact(arr: T[]): Array>`) A new array with all falsey values removed. +(`Array>`) A new array with all falsey values removed. ## Examples ```typescript -compact([0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); +compact([0, 0n, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); // Returns: [1, 2, 3, 4, 5] ``` diff --git a/docs/reference/array/maxBy.md b/docs/reference/array/maxBy.md index e0ba05bb2..f9d9596b2 100644 --- a/docs/reference/array/maxBy.md +++ b/docs/reference/array/maxBy.md @@ -23,4 +23,12 @@ function maxBy(items: T[], getValue: (element: T) => number): T | undefined; ```typescript maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } maxBy([], x => x.a); // Returns: undefined +maxBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // Returns: { name: 'john', age: 30 } ``` diff --git a/docs/reference/array/minBy.md b/docs/reference/array/minBy.md index 30341beee..82f8048f5 100644 --- a/docs/reference/array/minBy.md +++ b/docs/reference/array/minBy.md @@ -23,4 +23,12 @@ function minBy(items: T[], getValue: (element: T) => number): T | undefined; ```typescript minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } minBy([], x => x.a); // Returns: undefined +minBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // Returns: { name: 'joe', age: 26 } ``` diff --git a/docs/reference/compat/function/defer.md b/docs/reference/compat/function/defer.md new file mode 100644 index 000000000..feefad683 --- /dev/null +++ b/docs/reference/compat/function/defer.md @@ -0,0 +1,35 @@ +# defer + +::: 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). +::: + +Defers invoking the `func` until the current call stack has cleared. Any additional arguments are provided to `func` when it's invoked. + +## Signature + +```typescript +function defer any>(func: F, ...args: Parameters): number; +``` + +### Parameters + +- `func` (`F`): The function to defer. +- `args` (`Parameters`, optional): The arguments to invoke `func` with. + +### Returns + +(`number`): Returns the timer id. + +## Examples + +```typescript +import { defer } from 'es-toolkit/compat'; + +defer(text => { + console.log(text); +}, 'deferred'); +// => Logs 'deferred' after the current call stack has cleared. +``` diff --git a/docs/reference/compat/predicate/isFinite.md b/docs/reference/compat/predicate/isFinite.md new file mode 100644 index 000000000..1077a115f --- /dev/null +++ b/docs/reference/compat/predicate/isFinite.md @@ -0,0 +1,37 @@ +# isFinite + +::: 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). +::: + +Check if the given value is a finite number. + +This function can also serve as a type predicate in TypeScript, narrowing the type of the argument to `number`. + +## Signature + +```typescript +function isFinite(value: unknown): value is number; +``` + +### Parameters + +- `value`(`unknown`): The value to test if it is a finite number. + +### Returns + +(`value is number`): True if the value is a finite number, otherwise false. + +## Examples + +```typescript +const value1 = 100; +const value2 = Infinity; +const value3 = '100'; + +console.log(isFinite(value1)); // true +console.log(isFinite(value2)); // false +console.log(isFinite(value3)); // false +``` diff --git a/docs/reference/string/constantCase.md b/docs/reference/string/constantCase.md new file mode 100644 index 000000000..df85a6bc8 --- /dev/null +++ b/docs/reference/string/constantCase.md @@ -0,0 +1,32 @@ +# constantCase + +将字符串转换为常量命名法(constant case)。 + +常量命名法是一种命名约定,其中每个单词都以大写字母书写,并用下划线(\_)分隔。 + +例如 `CONSTANT_CASE`。 + +## 签名 + +```typescript +function constantCase(str: string): string; +``` + +### 参数 + +- `str` (`string`): 要转换为常量命名法的字符串。 + +### 返回值 + +(`string`) 转换后的常量命名法字符串。 + +## 示例 + +```typescript +import { constantCase } from 'es-toolkit/string'; + +constantCase('camelCase'); // 返回 'CAMEL_CASE' +constantCase('some whitespace'); // 返回 'SOME_WHITESPACE' +constantCase('hyphen-text'); // 返回 'HYPHEN_TEXT' +constantCase('HTTPRequest'); // 返回 'HTTP_REQUEST' +``` diff --git a/docs/reference/string/startCase.md b/docs/reference/string/startCase.md index 53cb68934..ac1df63bd 100644 --- a/docs/reference/string/startCase.md +++ b/docs/reference/string/startCase.md @@ -25,8 +25,8 @@ import { startCase } from 'es-toolkit/string'; startCase('--foo-bar--'); // returns 'Foo Bar' startCase('fooBar'); // returns 'Foo Bar' -startCase('__FOO_BAR__'); // returns 'FOO BAR' -startCase('XMLHttpRequest'); // returns 'XML Http Request' +startCase('__FOO_BAR__'); // returns 'Foo Bar' +startCase('XMLHttpRequest'); // returns 'Xml Http Request' startCase('_abc_123_def'); // returns 'Abc 123 Def' startCase('__abc__123__def__'); // returns 'Abc 123 Def' startCase('_-_-_-_'); // returns '' diff --git a/docs/zh_hans/reference/array/compact.md b/docs/zh_hans/reference/array/compact.md index 5cb8c6bf4..01fa421d7 100644 --- a/docs/zh_hans/reference/array/compact.md +++ b/docs/zh_hans/reference/array/compact.md @@ -1,11 +1,11 @@ # compact -从数组中删除假值(`false`、`null`、`0`、`''`、`undefined`、`NaN`)。 +从数组中删除假值(`false`、 `null`、 `0`、 `0n`, `''`、 `undefined`、 `NaN`)。 ## 签名 ```typescript -function compact(arr: T[]): Array>; +function compact(arr: T[]): Array>; ``` ### 参数 @@ -14,11 +14,11 @@ function compact(arr: T[]): Array>`) 移除所有假值后的新数组。 +(`Array>`) 移除所有假值后的新数组。 ## 示例 ```typescript -compact([0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); +compact([0, 0n, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); // 返回: [1, 2, 3, 4, 5] ``` diff --git a/docs/zh_hans/reference/array/maxBy.md b/docs/zh_hans/reference/array/maxBy.md index 7a15dba1b..0beac42ec 100644 --- a/docs/zh_hans/reference/array/maxBy.md +++ b/docs/zh_hans/reference/array/maxBy.md @@ -25,4 +25,12 @@ function maxBy(items: T[], getValue: (element: T) => number): T | undefined; ```typescript maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 返回: { a: 3 } maxBy([], x => x.a); // 返回: undefined +maxBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // 返回: { name: 'john', age: 30 } ``` diff --git a/docs/zh_hans/reference/array/minBy.md b/docs/zh_hans/reference/array/minBy.md index 641007f6d..f85056225 100644 --- a/docs/zh_hans/reference/array/minBy.md +++ b/docs/zh_hans/reference/array/minBy.md @@ -25,4 +25,12 @@ function minBy(items: T[], getValue: (element: T) => number): T | undefined; ```typescript minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // 返回: { a: 1 } minBy([], x => x.a); // 返回: undefined +minBy( + [ + { name: 'john', age: 30 }, + { name: 'jane', age: 28 }, + { name: 'joe', age: 26 }, + ], + x => x.age +); // 返回: { name: 'joe', age: 26 } ``` diff --git a/docs/zh_hans/reference/compat/function/defer.md b/docs/zh_hans/reference/compat/function/defer.md new file mode 100644 index 000000000..34332f38e --- /dev/null +++ b/docs/zh_hans/reference/compat/function/defer.md @@ -0,0 +1,36 @@ +# defer + +::: info +出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。 + +从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。 + +::: + +延迟调用 `func`,直到当前堆栈清理完毕。调用时,任何附加的参数会传给 `func`。 + +## 签名 + +```typescript +function defer any>(func: F, ...args: Parameters): number; +``` + +### 参数 + +- `func` (`F`): 要延迟调用的函数。 +- `args` (`Parameters`, 可选): 调用 `func` 时提供的参数。 + +### 返回值 + +(`number`): 返回计时器 ID。 + +## 示例 + +```typescript +import { defer } from 'es-toolkit/compat'; + +defer(text => { + console.log(text); +}, 'deferred'); +// => 在当前调用栈已清空后打印 'deferred'。 +``` diff --git a/docs/zh_hans/reference/compat/predicate/isFinite.md b/docs/zh_hans/reference/compat/predicate/isFinite.md new file mode 100644 index 000000000..d49037c3f --- /dev/null +++ b/docs/zh_hans/reference/compat/predicate/isFinite.md @@ -0,0 +1,39 @@ +# isFinite + +::: info + +出于兼容性原因,此函数仅在 `es-toolkit/compat` 中提供。它可能具有替代的原生 JavaScript API,或者尚未完全优化。 + +从 `es-toolkit/compat` 导入时,它的行为与 lodash 完全一致,并提供相同的功能,详情请见 [这里](../../../compatibility.md)。 + +::: + +检查给定的值是否是有限的数字。 + +此函数在 TypeScript 中也可以用作类型谓词,能够将参数的类型缩小为 `number`。 + +## 签名 + +```typescript +function isFinite(value: unknown): value is number; +``` + +### 参数 + +- `value`(`unknown`): 需要检查是否为有限数字的值。 + +### 返回值 + +(`value is number`): 如果值是有限的数字,返回 `true`,否则返回 `false`。 + +## 示例 + +```typescript +const value1 = 100; +const value2 = Infinity; +const value3 = '100'; + +console.log(isFinite(value1)); // true +console.log(isFinite(value2)); // false +console.log(isFinite(value3)); // false +``` diff --git a/docs/zh_hans/reference/string/constantCase.md b/docs/zh_hans/reference/string/constantCase.md new file mode 100644 index 000000000..2a56c01e8 --- /dev/null +++ b/docs/zh_hans/reference/string/constantCase.md @@ -0,0 +1,30 @@ +# constantCase + +Converts a string to constant case. + +Constant case is a naming convention where each word is written in uppercase letters and separated by an underscore (`_`). For example, `CONSTANT_CASE`. + +## Signature + +```typescript +function constantCase(str: string): string; +``` + +### Parameters + +- `str` (`string`): The string to convert to constant case. + +### Returns + +(`string`) The converted constant case string. + +## Examples + +```typescript +import { constantCase } from 'es-toolkit/string'; + +constantCase('camelCase'); // returns 'CAMEL_CASE' +constantCase('some whitespace'); // returns 'SOME_WHITESPACE' +constantCase('hyphen-text'); // returns 'HYPHEN_TEXT' +constantCase('HTTPRequest'); // returns 'HTTP_REQUEST' +``` diff --git a/docs/zh_hans/reference/string/startCase.md b/docs/zh_hans/reference/string/startCase.md index cba34c127..5ad3f0a1f 100644 --- a/docs/zh_hans/reference/string/startCase.md +++ b/docs/zh_hans/reference/string/startCase.md @@ -25,8 +25,8 @@ import { startCase } from 'es-toolkit/string'; startCase('--foo-bar--'); // returns 'Foo Bar' startCase('fooBar'); // returns 'Foo Bar' -startCase('__FOO_BAR__'); // returns 'FOO BAR' -startCase('XMLHttpRequest'); // returns 'XML Http Request' +startCase('__FOO_BAR__'); // returns 'Foo Bar' +startCase('XMLHttpRequest'); // returns 'Xml Http Request' startCase('_abc_123_def'); // returns 'Abc 123 Def' startCase('__abc__123__def__'); // returns 'Abc 123 Def' startCase('_-_-_-_'); // returns '' diff --git a/eslint.config.mjs b/eslint.config.mjs index 7e9426b0e..1e86bdb7e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,6 +4,7 @@ import tseslint from 'typescript-eslint'; import jsdoc from 'eslint-plugin-jsdoc'; import prettier from 'eslint-config-prettier'; import pluginVue from 'eslint-plugin-vue'; +import noForOfArrayPlugin from 'eslint-plugin-no-for-of-array'; export default [ { @@ -27,7 +28,6 @@ export default [ ...globals['shared-node-browser'], ...globals.es2015, }, - parserOptions: { ecmaFeatures: { jsx: true, @@ -42,6 +42,26 @@ export default [ prettier, jsdoc.configs['flat/recommended'], ...pluginVue.configs['flat/recommended'], + { + files: ['src/**/*.ts'], + ignores: ['**/*.spec.ts'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: import.meta.dirname, + ecmaFeatures: { + jsx: true, + }, + }, + }, + plugins: { + 'no-for-of-array': noForOfArrayPlugin, + }, + rules: { + 'no-for-of-array/no-for-of-array': 'error', + }, + }, { rules: { 'no-implicit-coercion': 'error', diff --git a/package.json b/package.json index bee569de8..a3e3ab36e 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "eslint": "^9.9.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsdoc": "^50.2.2", + "eslint-plugin-no-for-of-array": "^0.0.1", "eslint-plugin-vue": "^9.28.0", "execa": "^9.3.0", "globals": "^15.9.0", diff --git a/src/array/compact.ts b/src/array/compact.ts index 4c5d3b061..358dc7981 100644 --- a/src/array/compact.ts +++ b/src/array/compact.ts @@ -1,20 +1,21 @@ -type NotFalsey = Exclude; +type NotFalsey = Exclude; /** - * Removes falsey values (false, null, 0, '', undefined, NaN) from an array. + * Removes falsey values (false, null, 0, 0n, '', undefined, NaN) from an array. * * @template T - The type of elements in the array. * @param {T[]} arr - The input array to remove falsey values. - * @returns {Array>} - A new array with all falsey values removed. + * @returns {Array>} - A new array with all falsey values removed. * * @example - * compact([0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); + * compact([0, 0n, 1, false, 2, '', 3, null, undefined, 4, NaN, 5]); * Returns: [1, 2, 3, 4, 5] */ export function compact(arr: readonly T[]): Array> { const result: Array> = []; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; if (item) { result.push(item as NotFalsey); } diff --git a/src/array/countBy.ts b/src/array/countBy.ts index 6058cdfb8..acf1a7e9c 100644 --- a/src/array/countBy.ts +++ b/src/array/countBy.ts @@ -28,7 +28,8 @@ export function countBy(arr: readonly T[], mapper: (item: T) => K): Record { const result = {} as Record; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; const key = mapper(item); result[key] = (result[key] ?? 0) + 1; diff --git a/src/array/groupBy.ts b/src/array/groupBy.ts index 389e1ae66..ef0647cf2 100644 --- a/src/array/groupBy.ts +++ b/src/array/groupBy.ts @@ -33,7 +33,8 @@ export function groupBy(arr: readonly T[], getKeyFromItem: (item: T) => K): Record { const result = Object.create(null) as Record; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; const key = getKeyFromItem(item); if (result[key] == null) { diff --git a/src/array/keyBy.ts b/src/array/keyBy.ts index beea586fc..a3d05944b 100644 --- a/src/array/keyBy.ts +++ b/src/array/keyBy.ts @@ -28,7 +28,8 @@ export function keyBy(arr: readonly T[], getKeyFromItem: (item: T) => K): Record { const result = {} as Record; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; const key = getKeyFromItem(item); result[key] = item; } diff --git a/src/array/maxBy.ts b/src/array/maxBy.ts index a668a33f0..366aabdf2 100644 --- a/src/array/maxBy.ts +++ b/src/array/maxBy.ts @@ -9,6 +9,14 @@ * @example * maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } * maxBy([], x => x.a); // Returns: undefined + * maxBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'john', age: 30 } */ export function maxBy(items: readonly [T, ...T[]], getValue: (element: T) => number): T; /** @@ -22,6 +30,14 @@ export function maxBy(items: readonly [T, ...T[]], getValue: (element: T) => * @example * maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } * maxBy([], x => x.a); // Returns: undefined + * maxBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'john', age: 30 } */ export function maxBy(items: readonly T[], getValue: (element: T) => number): T | undefined; /** @@ -35,12 +51,21 @@ export function maxBy(items: readonly T[], getValue: (element: T) => number): * @example * maxBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 3 } * maxBy([], x => x.a); // Returns: undefined + * maxBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'john', age: 30 } */ export function maxBy(items: readonly T[], getValue: (element: T) => number): T { let maxElement = items[0]; let max = -Infinity; - for (const element of items) { + for (let i = 0; i < items.length; i++) { + const element = items[i]; const value = getValue(element); if (value > max) { max = value; diff --git a/src/array/minBy.ts b/src/array/minBy.ts index 55e40a75f..b4367bb7f 100644 --- a/src/array/minBy.ts +++ b/src/array/minBy.ts @@ -9,6 +9,14 @@ * @example * minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } * minBy([], x => x.a); // Returns: undefined + * minBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'joe', age: 26 } */ export function minBy(items: readonly [T, ...T[]], getValue: (element: T) => number): T; /** @@ -22,6 +30,14 @@ export function minBy(items: readonly [T, ...T[]], getValue: (element: T) => * @example * minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } * minBy([], x => x.a); // Returns: undefined + * minBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'joe', age: 26 } */ export function minBy(items: readonly T[], getValue: (element: T) => number): T | undefined; /** @@ -35,12 +51,21 @@ export function minBy(items: readonly T[], getValue: (element: T) => number): * @example * minBy([{ a: 1 }, { a: 2 }, { a: 3 }], x => x.a); // Returns: { a: 1 } * minBy([], x => x.a); // Returns: undefined + * minBy( + * [ + * { name: 'john', age: 30 }, + * { name: 'jane', age: 28 }, + * { name: 'joe', age: 26 }, + * ], + * x => x.age + * ); // Returns: { name: 'joe', age: 26 } */ export function minBy(items: readonly T[], getValue: (element: T) => number): T | undefined { let minElement = items[0]; let min = Infinity; - for (const element of items) { + for (let i = 0; i < items.length; i++) { + const element = items[i]; const value = getValue(element); if (value < min) { min = value; diff --git a/src/array/partition.ts b/src/array/partition.ts index 87dbef074..759d587bc 100644 --- a/src/array/partition.ts +++ b/src/array/partition.ts @@ -24,7 +24,8 @@ export function partition(arr: readonly T[], isInTruthy: (value: T) => boolea const truthy: T[] = []; const falsy: T[] = []; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; if (isInTruthy(item)) { truthy.push(item); } else { diff --git a/src/array/takeWhile.ts b/src/array/takeWhile.ts index c445feb2c..66e385cb9 100644 --- a/src/array/takeWhile.ts +++ b/src/array/takeWhile.ts @@ -19,7 +19,8 @@ export function takeWhile(arr: readonly T[], shouldContinueTaking: (element: T) => boolean): T[] { const result: T[] = []; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; if (!shouldContinueTaking(item)) { break; } diff --git a/src/array/unionBy.ts b/src/array/unionBy.ts index 036ab287d..f949636e1 100644 --- a/src/array/unionBy.ts +++ b/src/array/unionBy.ts @@ -23,7 +23,9 @@ export function unionBy(arr1: readonly T[], arr2: readonly T[], mapper: (item: T) => U): T[] { const map = new Map(); - for (const item of [...arr1, ...arr2]) { + const items = [...arr1, ...arr2]; + for (let i = 0; i < items.length; i++) { + const item = items[i]; const key = mapper(item); if (!map.has(key)) { diff --git a/src/array/uniqBy.ts b/src/array/uniqBy.ts index 39010c780..072ae1b92 100644 --- a/src/array/uniqBy.ts +++ b/src/array/uniqBy.ts @@ -27,7 +27,8 @@ export function uniqBy(arr: readonly T[], mapper: (item: T) => U): T[] { const map = new Map(); - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; const key = mapper(item); if (!map.has(key)) { diff --git a/src/array/uniqWith.ts b/src/array/uniqWith.ts index d33bc9e3d..71be1e677 100644 --- a/src/array/uniqWith.ts +++ b/src/array/uniqWith.ts @@ -16,7 +16,8 @@ export function uniqWith(arr: readonly T[], areItemsEqual: (item1: T, item2: T) => boolean): T[] { const result: T[] = []; - for (const item of arr) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; const isUniq = result.every(v => !areItemsEqual(v, item)); if (isUniq) { diff --git a/src/compat/_internal/MAX_INTEGER.ts b/src/compat/_internal/MAX_INTEGER.ts new file mode 100644 index 000000000..e1241f581 --- /dev/null +++ b/src/compat/_internal/MAX_INTEGER.ts @@ -0,0 +1 @@ +export const MAX_INTEGER = Number.MAX_VALUE; diff --git a/src/compat/_internal/MAX_SAFE_INTEGER.ts b/src/compat/_internal/MAX_SAFE_INTEGER.ts new file mode 100644 index 000000000..76be81a86 --- /dev/null +++ b/src/compat/_internal/MAX_SAFE_INTEGER.ts @@ -0,0 +1 @@ +export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; diff --git a/src/compat/_internal/normalizeForCase.ts b/src/compat/_internal/normalizeForCase.ts index 097763533..65573c931 100644 --- a/src/compat/_internal/normalizeForCase.ts +++ b/src/compat/_internal/normalizeForCase.ts @@ -6,6 +6,6 @@ export function normalizeForCase(str: unknown): string { str = toString(str); } - // Remove constraction apostrophes + // Remove contraction apostrophes return (str as string).replace(/['\u2019]/g, ''); } diff --git a/src/compat/array/castArray.spec.ts b/src/compat/array/castArray.spec.ts index 6be550392..3cdd930ec 100644 --- a/src/compat/array/castArray.spec.ts +++ b/src/compat/array/castArray.spec.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from 'vitest'; import { castArray } from './castArray'; +/** + * @see https://github.com/lodash/lodash/blob/6a2cc1dfcf7634fea70d1bc5bd22db453df67b42/test/castArray.spec.js#L1 + */ describe('castArray', () => { it('should wrap non-array items in an array', () => { const falsey = [false, null, undefined, 0, NaN, '']; diff --git a/src/compat/array/castArray.ts b/src/compat/array/castArray.ts index 43a9c6b5d..e62fdd64c 100644 --- a/src/compat/array/castArray.ts +++ b/src/compat/array/castArray.ts @@ -2,7 +2,7 @@ * Casts value as an array if it's not one. * * @template T The type of elements in the array. - * @param {T | readonly T[]} value The value to be cast to an array. + * @param {T | T[]} value The value to be cast to an array. * @returns {T[]} An array containing the input value if it wasn't an array, or the original array if it was. * * @example @@ -15,7 +15,7 @@ * const arr3 = castArray({'a': 1}); * // Returns: [{'a': 1}] * - * const arr4 = castArray(null); + * const arr4 = castArray(null); * // Returns: [null] * * const arr5 = castArray(undefined); diff --git a/src/compat/function/debounce.spec.ts b/src/compat/function/debounce.spec.ts index 97bb6e405..5a7b43345 100644 --- a/src/compat/function/debounce.spec.ts +++ b/src/compat/function/debounce.spec.ts @@ -76,7 +76,7 @@ describe('debounce', () => { expect(func).toHaveBeenCalledTimes(2); }); - it('should have no effect if we call cancel when the function is not executed', async () => { + it('should have no effect if we call cancel when the function is not executed', () => { const func = vi.fn(); const debounceMs = 50; const debouncedFunc = debounce(func, debounceMs); @@ -231,7 +231,7 @@ describe('debounce', () => { expect(callCount).toBe(2); }); - it('subsequent debounced calls return the last `func` result', async done => { + it('subsequent debounced calls return the last `func` result', async () => { const debounced = debounce(identity, 32); debounced('a'); @@ -383,7 +383,7 @@ describe('debounce', () => { expect(callCount).toBe(2); }); - it('should support `maxWait` in a tight loop', async done => { + it('should support `maxWait` in a tight loop', async () => { const limit = 1000; let withCount = 0; let withoutCount = 0; @@ -460,10 +460,12 @@ describe('debounce', () => { const object = {}; const debounced = debounce( - function (this: any, value: any) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function (this: any, _: any) { actual = [this]; + // eslint-disable-next-line prefer-rest-params Array.prototype.push.apply(actual, arguments as any); - return ++callCount != 2; + return ++callCount !== 2; }, 32, { leading: true, maxWait: 64 } @@ -505,7 +507,7 @@ describe('debounce', () => { expect(callCount).toBe(isDebounce ? 1 : 2); }); - it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async done => { + it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async () => { const actual: any[] = []; const object = { funced: func(function (this: any) { @@ -526,8 +528,10 @@ describe('debounce', () => { const expected = args.slice(); const queue: any[] = args.slice(); - var funced = func(function (this: any, _: unknown) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const funced = func(function (this: any, _: unknown) { const current = [this]; + // eslint-disable-next-line prefer-rest-params Array.prototype.push.apply(current, arguments as any); actual.push(current); diff --git a/src/compat/function/defer.spec.ts b/src/compat/function/defer.spec.ts new file mode 100644 index 000000000..252fba9ba --- /dev/null +++ b/src/compat/function/defer.spec.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { defer } from './defer'; + +describe('defer', () => { + it('should provide additional arguments to `func`', (done: () => void) => { + let args: any[]; + + defer( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + function (a: any, b: any) { + // eslint-disable-next-line prefer-rest-params + args = Array.from(arguments); + }, + 1, + 2 + ); + + setTimeout(() => { + expect(args).toEqual([1, 2]); + done(); + }, 32); + }); + + it('should be cancelable', (done: () => void) => { + let pass = true; + const timerId = defer(() => { + pass = false; + }); + + clearTimeout(timerId); + + setTimeout(() => { + expect(pass); + done(); + }, 32); + }); +}); diff --git a/src/compat/function/defer.ts b/src/compat/function/defer.ts new file mode 100644 index 000000000..90087bb33 --- /dev/null +++ b/src/compat/function/defer.ts @@ -0,0 +1,19 @@ +/** + * Defers invoking the `func` until the current call stack has cleared. Any additional arguments are provided to func when it's invoked. + * + * @param {F} func The function to defer. + * @param {Parameters} args The arguments to invoke `func` with. + * @returns {number} Returns the timer id. + * + * @example + * defer((text) => { + * console.log(text); + * }, 'deferred'); + * // => Logs 'deferred' after the current call stack has cleared. + */ +export function defer any>(func: F, ...args: Parameters): number { + if (typeof func !== 'function') { + throw new TypeError('Expected a function'); + } + return setTimeout(func, 1, ...args); +} diff --git a/src/compat/function/throttle.spec.ts b/src/compat/function/throttle.spec.ts index 5e5a96cc6..7c7495e87 100644 --- a/src/compat/function/throttle.spec.ts +++ b/src/compat/function/throttle.spec.ts @@ -39,9 +39,8 @@ describe('throttle', () => { expect(results2[0]).not.toStrictEqual(undefined); }); - it('should clear timeout when `func` is called', async done => { + it('should clear timeout when `func` is called', async () => { let callCount = 0; - let dateCount = 0; const throttled = throttle(() => { callCount++; @@ -81,7 +80,7 @@ describe('throttle', () => { options ); - const start = +new Date(); + const start = Number(new Date()); while (Date.now() - start < limit) { throttled(); } @@ -229,7 +228,7 @@ describe('throttle', () => { expect(callCount).toBe(isDebounce ? 1 : 2); }); - it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async done => { + it(`\`_.${methodName}\` should invoke \`func\` with the correct \`this\` binding`, async () => { const actual: any[] = []; const object = { funced: func(function (this: any) { @@ -250,8 +249,10 @@ describe('throttle', () => { const expected = args.slice(); const queue: any[] = args.slice(); - var funced = func(function (this: any, _: unknown) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const funced = func(function (this: any, _: unknown) { const current = [this]; + // eslint-disable-next-line prefer-rest-params Array.prototype.push.apply(current, arguments as any); actual.push(current); diff --git a/src/compat/index.ts b/src/compat/index.ts index 122111bcf..4df778551 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -48,6 +48,7 @@ export { head as first } from '../array/head.ts'; export { ary } from './function/ary.ts'; export { bind } from './function/bind.ts'; export { bindKey } from './function/bindKey.ts'; +export { defer } from './function/defer.ts'; export { rest } from './function/rest.ts'; export { spread } from './function/spread.ts'; export { attempt } from './function/attempt.ts'; @@ -80,6 +81,7 @@ export { isObject } from './predicate/isObject.ts'; export { isObjectLike } from './predicate/isObjectLike.ts'; export { isBoolean } from './predicate/isBoolean.ts'; export { isError } from './predicate/isError.ts'; +export { isFinite } from './predicate/isFinite.ts'; export { isTypedArray } from './predicate/isTypedArray.ts'; export { isMatch } from './predicate/isMatch.ts'; export { isRegExp } from './predicate/isRegExp.ts'; @@ -105,6 +107,7 @@ export { lowerCase } from './string/lowerCase.ts'; export { upperCase } from './string/upperCase.ts'; export { startsWith } from './string/startsWith.ts'; export { endsWith } from './string/endsWith.ts'; +export { pad } from './string/pad.ts'; export { padStart } from './string/padStart.ts'; export { padEnd } from './string/padEnd.ts'; export { repeat } from './string/repeat.ts'; @@ -125,3 +128,6 @@ export { random } from './math/random.ts'; export { toPath } from './util/toPath.ts'; export { toString } from './util/toString.ts'; +export { toNumber } from './util/toNumber.ts'; +export { toInteger } from './util/toInteger.ts'; +export { toFinite } from './util/toFinite.ts'; diff --git a/src/compat/math/max.ts b/src/compat/math/max.ts index bdd74270f..43663bf71 100644 --- a/src/compat/math/max.ts +++ b/src/compat/math/max.ts @@ -37,7 +37,8 @@ export function max(items: readonly T[] = []): T | undefined { let maxElement = items[0]; let max: any = undefined; - for (const element of items) { + for (let i = 0; i < items.length; i++) { + const element = items[i]; if (max == null || element > max) { max = element; maxElement = element; diff --git a/src/compat/math/min.ts b/src/compat/math/min.ts index a9ccfa3ec..695993dfa 100644 --- a/src/compat/math/min.ts +++ b/src/compat/math/min.ts @@ -41,7 +41,8 @@ export function min(items: readonly T[] = []): T { let minElement = items[0]; let min: any = undefined; - for (const element of items) { + for (let i = 0; i < items.length; i++) { + const element = items[i]; if (min == null || element < min) { min = element; minElement = element; diff --git a/src/compat/math/random.ts b/src/compat/math/random.ts index 6911da98c..6b44b78d7 100644 --- a/src/compat/math/random.ts +++ b/src/compat/math/random.ts @@ -68,9 +68,9 @@ export function random(minimum: number, maximum: number, floating?: boolean): nu * const result3 = random(5, 5); // If the minimum is equal to the maximum, an error is thrown. */ export function random(...args: any[]): number { - let minimum: number = 0; - let maximum: number = 1; - let floating: boolean = false; + let minimum = 0; + let maximum = 1; + let floating = false; switch (args.length) { case 1: { @@ -91,6 +91,7 @@ export function random(...args: any[]): number { maximum = args[1]; } } + // eslint-disable-next-line no-fallthrough case 3: { if (typeof args[2] === 'object' && args[2] != null && args[2][args[1]] === args[0]) { minimum = 0; diff --git a/src/compat/object/pick.ts b/src/compat/object/pick.ts index 30a21de01..bdf567d34 100644 --- a/src/compat/object/pick.ts +++ b/src/compat/object/pick.ts @@ -94,7 +94,8 @@ export function pick< const result: any = {}; - for (let keys of keysArr) { + for (let i = 0; i < keysArr.length; i++) { + let keys = keysArr[i]; switch (typeof keys) { case 'object': { if (!Array.isArray(keys)) { diff --git a/src/compat/predicate/conformsTo.ts b/src/compat/predicate/conformsTo.ts index d6933a46a..52bf2f16c 100644 --- a/src/compat/predicate/conformsTo.ts +++ b/src/compat/predicate/conformsTo.ts @@ -36,7 +36,9 @@ export function conformsTo( return Object.keys(source).length === 0; } - for (const key of Object.keys(source)) { + const keys = Object.keys(source); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; const predicate = source[key]; const value = target[key]; if ((value === undefined && !(key in target)) || !predicate(value)) { diff --git a/src/compat/predicate/isFinite.spec.ts b/src/compat/predicate/isFinite.spec.ts new file mode 100644 index 000000000..c19a3cf21 --- /dev/null +++ b/src/compat/predicate/isFinite.spec.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest'; +import { isFinite } from './isFinite'; + +describe('isFinite', () => { + it("should return 'true' for finite values", () => { + expect(isFinite(1)).toBe(true); + expect(isFinite(1.123)).toBe(true); + expect(isFinite(-1)).toBe(true); + }); + + it("should return 'false' for not-finite values", () => { + expect(isFinite(Infinity)).toBe(false); + expect(isFinite(-Infinity)).toBe(false); + expect(isFinite(NaN)).toBe(false); + expect(isFinite(Object(1))).toBe(false); + }); + + it("should return 'false' for non-numeric values", () => { + expect(isFinite(undefined)).toBe(false); + expect(isFinite([])).toBe(false); + expect(isFinite(true)).toBe(false); + expect(isFinite('')).toBe(false); + expect(isFinite(' ')).toBe(false); + expect(isFinite('2px')).toBe(false); + }); + + it("should return 'false' for numeric string values", () => { + expect(isFinite('2')).toBe(false); + expect(isFinite('0')).toBe(false); + expect(isFinite('Infinity')).toBe(false); + expect(isFinite('-1')).toBe(false); + }); +}); diff --git a/src/compat/predicate/isFinite.ts b/src/compat/predicate/isFinite.ts new file mode 100644 index 000000000..9515b4cf1 --- /dev/null +++ b/src/compat/predicate/isFinite.ts @@ -0,0 +1,20 @@ +/** + * Checks if `value` is a finite number. + * + * @param {unknown} value The value to check. + * @returns {value is number} Returns `true` if `value` is a finite number, `false` otherwise. + * + * @example + * ```typescript + * const value1 = 100; + * const value2 = Infinity; + * const value3 = '100'; + * + * console.log(isFinite(value1)); // true + * console.log(isFinite(value2)); // false + * console.log(isFinite(value3)); // false + * ``` + */ +export function isFinite(value: unknown): value is number { + return Number.isFinite(value); +} diff --git a/src/compat/string/endsWith.spec.ts b/src/compat/string/endsWith.spec.ts index c2382d2f6..f9840c7fc 100644 --- a/src/compat/string/endsWith.spec.ts +++ b/src/compat/string/endsWith.spec.ts @@ -2,31 +2,31 @@ import { describe, it, expect } from 'vitest'; import { endsWith } from './endsWith'; describe('endsWith', () => { - it('should return true if the string ends with the target string', async () => { + it('should return true if the string ends with the target string', () => { expect(endsWith('fooBar', 'Bar')).toEqual(true); }); - it('should return false if the string does not end with the target string', async () => { + it('should return false if the string does not end with the target string', () => { expect(endsWith('fooBar', 'abc')).toEqual(false); }); - it('should return false if the string does not end with the target string, but does contain it', async () => { + it('should return false if the string does not end with the target string, but does contain it', () => { expect(endsWith('fooBar', 'foo')).toEqual(false); }); - it('should return true if the target string is an empty string', async () => { + it('should return true if the target string is an empty string', () => { expect(endsWith('fooBar', '')).toEqual(true); }); - it('should return true if the string and target string are empty strings', async () => { + it('should return true if the string and target string are empty strings', () => { expect(endsWith('', '')).toEqual(true); }); - it('should return false if the string past the provided position does not end with the target string', async () => { + it('should return false if the string past the provided position does not end with the target string', () => { expect(endsWith('fooBar', 'foo', 5)).toEqual(false); }); - it('should return true if the string before the provided position ends with the target string', async () => { + it('should return true if the string before the provided position ends with the target string', () => { expect(endsWith('fooBar123', 'foo', 3)).toEqual(true); }); diff --git a/src/compat/string/pad.spec.ts b/src/compat/string/pad.spec.ts new file mode 100644 index 000000000..d5408c4fc --- /dev/null +++ b/src/compat/string/pad.spec.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from 'vitest'; +import { pad } from './pad'; + +describe('pad', () => { + it(`\`pad\` should not pad if string is >= \`length\``, () => { + expect(pad('abc', 2)).toBe('abc'); + expect(pad('abc', 3)).toBe('abc'); + }); + + it(`\`pad\` should treat negative \`length\` as \`0\``, () => { + [0, -2].forEach(length => { + expect(pad('abc', length)).toBe('abc'); + }); + }); + + it(`\`pad\` should coerce \`length\` to a number`, () => { + ['', '4'].forEach(length => { + const actual = length ? 'abc ' : 'abc'; + // @ts-expect-error - invalid length + expect(pad('abc', length)).toBe(actual); + }); + }); + + it(`\`pad\` should treat nullish values as empty strings`, () => { + [undefined, '_-'].forEach(chars => { + const expected = chars ? '__' : ' '; + // @ts-expect-error - invalid string + expect(pad(null, 2, chars)).toBe(expected); + // @ts-expect-error - invalid string + expect(pad(undefined, 2, chars)).toBe(expected); + expect(pad('', 2, chars)).toBe(expected); + }); + }); + + it(`\`pad\` should return \`string\` when \`chars\` coerces to an empty string`, () => { + const values = ['', Object('')]; + const expected = values.map(() => 'abc'); + + const actual = values.map(value => pad('abc', 6, value)); + + expect(actual).toEqual(expected); + }); + + it('should pad a string to a given length', () => { + // eslint-disable-next-line no-sparse-arrays + const values = [, undefined]; + const expected = values.map(() => ' abc '); + + const actual = values.map((value, index) => (index ? pad('abc', 6, value) : pad('abc', 6))); + + expect(actual).toEqual(expected); + }); + + it('should truncate pad characters to fit the pad length', () => { + expect(pad('abc', 8)).toBe(' abc '); + expect(pad('abc', 8, '_-')).toBe('_-abc_-_'); + }); + + it('should coerce `string` to a string', () => { + const values = [Object('abc'), { toString: () => 'abc' }]; + const expected = values.map(() => true); + + const actual = values.map(value => pad(value, 6) === ' abc '); + + expect(actual).toEqual(expected); + }); +}); diff --git a/src/compat/string/pad.ts b/src/compat/string/pad.ts new file mode 100644 index 000000000..d1836f8c0 --- /dev/null +++ b/src/compat/string/pad.ts @@ -0,0 +1,21 @@ +import { pad as padToolkit } from '../../string/pad.ts'; +import { toString } from '../util/toString.ts'; +/** + * Pads string on the left and right sides if it's shorter than length. Padding characters are truncated if they can't be evenly divided by length. + * If the length is less than or equal to the original string's length, or if the padding character is an empty string, the original string is returned unchanged. + * + * @param {string} str - The string to pad. + * @param {number} [length] - The length of the resulting string once padded. + * @param {string} [chars] - The character(s) to use for padding. + * @returns {string} - The padded string, or the original string if padding is not required. + * + * @example + * const result1 = pad('abc', 8); // result will be ' abc ' + * const result2 = pad('abc', 8, '_-'); // result will be '_-abc_-_' + * const result3 = pad('abc', 3); // result will be 'abc' + * const result4 = pad('abc', 2); // result will be 'abc' + * + */ +export function pad(str: string, length: number, chars = ' '): string { + return padToolkit(toString(str), length, chars); +} diff --git a/src/compat/string/startsWith.spec.ts b/src/compat/string/startsWith.spec.ts index 5a1db9182..4c5e5ed39 100644 --- a/src/compat/string/startsWith.spec.ts +++ b/src/compat/string/startsWith.spec.ts @@ -2,31 +2,31 @@ import { describe, it, expect } from 'vitest'; import { startsWith } from './startsWith'; describe('startsWith', () => { - it('should return true if the string starts with the target string', async () => { + it('should return true if the string starts with the target string', () => { expect(startsWith('fooBar', 'foo')).toEqual(true); }); - it('should return false if the string does not start with the target string', async () => { + it('should return false if the string does not start with the target string', () => { expect(startsWith('fooBar', 'abc')).toEqual(false); }); - it('should return false if the string does not start with the target string, but does contain it', async () => { + it('should return false if the string does not start with the target string, but does contain it', () => { expect(startsWith('fooBar', 'Bar')).toEqual(false); }); - it('should return true if the target string is an empty string', async () => { + it('should return true if the target string is an empty string', () => { expect(startsWith('fooBar', '')).toEqual(true); }); - it('should return true if the string and target string are empty strings', async () => { + it('should return true if the string and target string are empty strings', () => { expect(startsWith('', '')).toEqual(true); }); - it('should return false if the string past the provided position does not start with the target string', async () => { + it('should return false if the string past the provided position does not start with the target string', () => { expect(startsWith('fooBar', 'Bar', 5)).toEqual(false); }); - it('should return true if the string past the provided position does start with the target string', async () => { + it('should return true if the string past the provided position does start with the target string', () => { expect(startsWith('fooBar', 'Bar', 3)).toEqual(true); }); diff --git a/src/compat/util/toFinite.spec.ts b/src/compat/util/toFinite.spec.ts new file mode 100644 index 000000000..150bba3d1 --- /dev/null +++ b/src/compat/util/toFinite.spec.ts @@ -0,0 +1,194 @@ +import { describe, it, expect } from 'vitest'; +import { falsey } from '../_internal/falsey'; +import { symbol } from '../_internal/symbol'; +import { MAX_INTEGER } from '../_internal/MAX_INTEGER'; +import { MAX_SAFE_INTEGER } from '../_internal/MAX_SAFE_INTEGER'; +import { whitespace } from '../_internal/whitespace'; +import { identity } from '../_internal/identity'; +import { flatMap } from '../../array/flatMap'; +import { toFinite } from './toFinite'; + +describe('toFinite', () => { + it(`should preserve the sign of \`0\``, () => { + const values = [0, '0', -0, '-0']; + const expected = [ + [0, Infinity], + [0, Infinity], + [-0, -Infinity], + [-0, -Infinity], + ]; + + [0, 1].forEach(index => { + const others = values.map(index ? Object : identity); + + const actual = others.map(value => { + const result = toFinite(value); + return [result, 1 / result]; + }); + + expect(actual).toEqual(expected); + }); + }); + + function negative(string: string) { + return `-${string}`; + } + + function pad(string: string) { + return whitespace + string + whitespace; + } + + function positive(string: string) { + return `+${string}`; + } + + it(`should pass thru primitive number values`, () => { + const values = [0, 1, NaN]; + const expected = [0, 1, 0]; + const actual = values.map(toFinite); + + expect(actual).toEqual(expected); + }); + + it(`should convert number primitives and objects to numbers`, () => { + const values = [2, 1.2, MAX_SAFE_INTEGER, MAX_INTEGER, Infinity, NaN]; + + const expected = values.map(value => { + if (value === Infinity) { + value = MAX_INTEGER; + } else if (value !== value) { + value = 0; + } + + const neg = value === 0 ? 0 : -value; + return [value, value, neg, neg]; + }); + + const actual = values.map(value => [ + toFinite(value), + toFinite(Object(value)), + toFinite(-value), + toFinite(Object(-value)), + ]); + + expect(actual).toEqual(expected); + }); + + it(`should convert string primitives and objects to numbers`, () => { + const transforms = [identity, pad, positive, negative]; + + const values = [ + '10', + '1.234567890', + `${MAX_SAFE_INTEGER}`, + '1e+308', + '1e308', + '1E+308', + '1E308', + '5e-324', + '5E-324', + 'Infinity', + 'NaN', + ]; + + const expected = values.map(value => { + let n = Number(value); + if (n === Infinity) { + n = MAX_INTEGER; + } else if (n !== n) { + n = 0; + } + const neg = n === 0 ? 0 : -n; + return [n, n, n, n, n, n, neg, neg]; + }); + + const actual = values.map(value => + flatMap(transforms, mod => [toFinite(mod(value)), toFinite(Object(mod(value)))]) + ); + + expect(actual).toEqual(expected); + }); + + it(`should convert binary/octal strings to numbers`, () => { + const numbers = [42, 5349, 1715004]; + const transforms = [identity, pad]; + const values = ['0b101010', '0o12345', '0x1a2b3c']; + + const expected = numbers.map(n => [n, n, n, n, n, n, n, n]); + + const actual = values.map(value => { + const upper = value.toUpperCase(); + return flatMap(transforms, mod => [ + toFinite(mod(value)), + toFinite(Object(mod(value))), + toFinite(mod(upper)), + toFinite(Object(mod(upper))), + ]); + }); + + expect(actual).toEqual(expected); + }); + + it(`should convert invalid binary/octal strings to '0'`, () => { + const transforms = [identity, pad, positive, negative]; + const values = ['0b', '0o', '0x', '0b1010102', '0o123458', '0x1a2b3x']; + + const expected = values.map(() => [0, 0, 0, 0, 0, 0, 0, 0]); + + const actual = values.map(value => + flatMap(transforms, mod => [toFinite(mod(value)), toFinite(Object(mod(value)))]) + ); + + expect(actual).toEqual(expected); + }); + + it(`should convert symbols to '0'`, () => { + const object1 = Object(symbol); + const object2 = Object(symbol); + const values = [symbol, object1, object2]; + const expected = values.map(() => 0); + + object2.valueOf = undefined; + const actual = values.map(toFinite); + + expect(actual).toEqual(expected); + }); + + it(`should convert empty values to \`0\` or \`NaN\``, () => { + const values = falsey.concat(whitespace); + + const expected = values.map(() => 0); + + const actual = values.map((value, index) => (index ? toFinite(value) : toFinite())); + + expect(actual).toEqual(expected); + }); + + it(`should coerce objects to numbers`, () => { + const values: any = [ + {}, + [], + [1], + [1, 2], + { valueOf: '1.1' }, + { valueOf: '1.1', toString: () => '2.2' }, + { valueOf: () => '1.1', toString: '2.2' }, + { + valueOf: () => '1.1', + toString: () => '2.2', + }, + { valueOf: () => '-0x1a2b3c' }, + { toString: () => '-0x1a2b3c' }, + { valueOf: () => '0o12345' }, + { toString: () => '0o12345' }, + { valueOf: () => '0b101010' }, + { toString: () => '0b101010' }, + ]; + + const expected = [0, 0, 1, 0, 0, 2.2, 1.1, 1.1, 0, 0, 5349, 5349, 42, 42]; + + const actual = values.map(toFinite); + + expect(actual).toEqual(expected); + }); +}); diff --git a/src/compat/util/toFinite.ts b/src/compat/util/toFinite.ts new file mode 100644 index 000000000..ee85ba34b --- /dev/null +++ b/src/compat/util/toFinite.ts @@ -0,0 +1,30 @@ +import { toNumber } from './toNumber'; + +/** + * Converts `value` to a finite number. + * + * @param {unknown} value - The value to convert. + * @returns {number} Returns the number. + * + * @example + * toNumber(3.2); // => 3.2 + * toNumber(Number.MIN_VALUE); // => 5e-324 + * toNumber(Infinity); // => 1.7976931348623157e+308 + * toNumber('3.2'); // => 3.2 + * toNumber(Symbol.iterator); // => 0 + * toNumber(NaN); // => 0 + */ +export function toFinite(value?: unknown): number { + if (!value) { + return value === 0 ? value : 0; + } + + value = toNumber(value); + + if (value === Infinity || value === -Infinity) { + const sign = value < 0 ? -1 : 1; + return sign * Number.MAX_VALUE; + } + + return value === value ? (value as number) : 0; +} diff --git a/src/compat/util/toInteger.spec.ts b/src/compat/util/toInteger.spec.ts new file mode 100644 index 000000000..9169e697a --- /dev/null +++ b/src/compat/util/toInteger.spec.ts @@ -0,0 +1,213 @@ +import { describe, it, expect } from 'vitest'; +import { falsey } from '../_internal/falsey'; +import { symbol } from '../_internal/symbol'; +import { MAX_INTEGER } from '../_internal/MAX_INTEGER'; +import { MAX_SAFE_INTEGER } from '../_internal/MAX_SAFE_INTEGER'; +import { whitespace } from '../_internal/whitespace'; +import { identity } from '../_internal/identity'; +import { flatMap } from '../../array/flatMap'; +import { toInteger } from './toInteger'; + +describe('toInteger', () => { + it(`should preserve the sign of \`0\``, () => { + const values = [0, '0', -0, '-0']; + const expected = [ + [0, Infinity], + [0, Infinity], + [-0, -Infinity], + [-0, -Infinity], + ]; + + [0, 1].forEach(index => { + const others = values.map(index ? Object : identity); + + const actual = others.map(value => { + const result = toInteger(value); + return [result, 1 / result]; + }); + + expect(actual).toEqual(expected); + }); + }); + + function negative(string: string) { + return `-${string}`; + } + + function pad(string: string) { + return whitespace + string + whitespace; + } + + function positive(string: string) { + return `+${string}`; + } + + it(`should pass thru primitive number values`, () => { + const values = [0, 1, NaN]; + const expected = [0, 1, 0]; + const actual = values.map(toInteger); + + expect(actual).toEqual(expected); + }); + + it(`should convert number primitives and objects to numbers`, () => { + const values = [2, 1.2, MAX_SAFE_INTEGER, MAX_INTEGER, Infinity, NaN]; + + const expected = values.map(value => { + if (value === 1.2) { + value = 1; + } else if (value === Infinity) { + value = MAX_INTEGER; + } else if (value !== value) { + value = 0; + } + + const neg = value === 0 ? 0 : -value; + return [value, value, neg, neg]; + }); + + const actual = values.map(value => [ + toInteger(value), + toInteger(Object(value)), + toInteger(-value), + toInteger(Object(-value)), + ]); + + expect(actual).toEqual(expected); + }); + + it(`should convert string primitives and objects to numbers`, () => { + const transforms = [identity, pad, positive, negative]; + + const values = [ + '10', + '1.234567890', + `${MAX_SAFE_INTEGER}`, + '1e+308', + '1e308', + '1E+308', + '1E308', + '5e-324', + '5E-324', + 'Infinity', + 'NaN', + ]; + + const expected = values.map(value => { + let n = Number(value); + if (n === 1.23456789) { + n = 1; + } else if (n === Infinity) { + n = MAX_INTEGER; + } else if (n === Number.MIN_VALUE || n !== n) { + n = 0; + } + const neg = n === 0 ? 0 : -n; + return [n, n, n, n, n, n, neg, neg]; + }); + + const actual = values.map(value => + flatMap(transforms, mod => [toInteger(mod(value)), toInteger(Object(mod(value)))]) + ); + + expect(actual).toEqual(expected); + }); + + it(`should convert binary/octal strings to numbers`, () => { + const numbers = [42, 5349, 1715004]; + const transforms = [identity, pad]; + const values = ['0b101010', '0o12345', '0x1a2b3c']; + + const expected = numbers.map(n => [n, n, n, n, n, n, n, n]); + + const actual = values.map(value => { + const upper = value.toUpperCase(); + return flatMap(transforms, mod => [ + toInteger(mod(value)), + toInteger(Object(mod(value))), + toInteger(mod(upper)), + toInteger(Object(mod(upper))), + ]); + }); + + expect(actual).toEqual(expected); + }); + + it(`should convert invalid binary/octal strings to '0'`, () => { + const transforms = [identity, pad, positive, negative]; + const values = ['0b', '0o', '0x', '0b1010102', '0o123458', '0x1a2b3x']; + + const expected = values.map(() => [0, 0, 0, 0, 0, 0, 0, 0]); + + const actual = values.map(value => + flatMap(transforms, mod => [toInteger(mod(value)), toInteger(Object(mod(value)))]) + ); + + expect(actual).toEqual(expected); + }); + + it(`should convert symbols to '0'`, () => { + const object1 = Object(symbol); + const object2 = Object(symbol); + const values = [symbol, object1, object2]; + const expected = values.map(() => 0); + + object2.valueOf = undefined; + const actual = values.map(toInteger); + + expect(actual).toEqual(expected); + }); + + it(`should convert empty values to \`0\` or \`NaN\``, () => { + const values = falsey.concat(whitespace); + + const expected = values.map(() => 0); + + const actual = values.map((value, index) => (index ? toInteger(value) : toInteger())); + + expect(actual).toEqual(expected); + }); + + it(`should coerce objects to numbers`, () => { + const values: any = [ + {}, + [], + [1], + [1, 2], + { valueOf: '1.1' }, + { valueOf: '1.1', toString: () => '2.2' }, + { valueOf: () => '1.1', toString: '2.2' }, + { + valueOf: () => '1.1', + toString: () => '2.2', + }, + { valueOf: () => '-0x1a2b3c' }, + { toString: () => '-0x1a2b3c' }, + { valueOf: () => '0o12345' }, + { toString: () => '0o12345' }, + { valueOf: () => '0b101010' }, + { toString: () => '0b101010' }, + ]; + + const expected = [0, 0, 1, 0, 0, 2, 1, 1, 0, 0, 5349, 5349, 42, 42]; + + const actual = values.map(toInteger); + + expect(actual).toEqual(expected); + }); + + it(`should convert values to integers`, () => { + expect(toInteger(-5.6)).toBe(-5); + expect(toInteger('5.6')).toBe(5); + expect(toInteger()).toBe(0); + expect(toInteger(NaN)).toBe(0); + + const expected = MAX_INTEGER; + expect(toInteger(Infinity)).toBe(expected); + expect(toInteger(-Infinity)).toBe(-expected); + }); + + it(`should support \`value\` of \`-0\``, () => { + expect(1 / toInteger(-0)).toBe(-Infinity); + }); +}); diff --git a/src/compat/util/toInteger.ts b/src/compat/util/toInteger.ts new file mode 100644 index 000000000..ddcfefac6 --- /dev/null +++ b/src/compat/util/toInteger.ts @@ -0,0 +1,22 @@ +import { toFinite } from './toFinite'; + +/** + * Converts `value` to an integer. + * + * @param {unknown} value - The value to convert. + * @returns {number} Returns the number. + * + * @example + * toInteger(3.2); // => 3 + * toInteger(Number.MIN_VALUE); // => 0 + * toInteger(Infinity); // => 1.7976931348623157e+308 + * toInteger('3.2'); // => 3 + * toInteger(Symbol.iterator); // => 0 + * toInteger(NaN); // => 0 + */ +export function toInteger(value?: unknown): number { + const finite = toFinite(value); + const remainder = finite % 1; + + return remainder ? finite - remainder : finite; +} diff --git a/src/compat/util/toNumber.spec.ts b/src/compat/util/toNumber.spec.ts new file mode 100644 index 000000000..91e2e17ae --- /dev/null +++ b/src/compat/util/toNumber.spec.ts @@ -0,0 +1,183 @@ +import { describe, it, expect } from 'vitest'; +import { falsey } from '../_internal/falsey'; +import { symbol } from '../_internal/symbol'; +import { MAX_INTEGER } from '../_internal/MAX_INTEGER'; +import { MAX_SAFE_INTEGER } from '../_internal/MAX_SAFE_INTEGER'; +import { whitespace } from '../_internal/whitespace'; +import { identity } from '../_internal/identity'; +import { flatMap } from '../../array/flatMap'; +import { toNumber } from './toNumber'; + +describe('toNumber', () => { + it(`should preserve the sign of \`0\``, () => { + const values = [0, '0', -0, '-0']; + const expected = [ + [0, Infinity], + [0, Infinity], + [-0, -Infinity], + [-0, -Infinity], + ]; + + [0, 1].forEach(index => { + const others = values.map(index ? Object : identity); + + const actual = others.map(value => { + const result = toNumber(value); + return [result, 1 / result]; + }); + + expect(actual).toEqual(expected); + }); + }); + + function negative(string: string) { + return `-${string}`; + } + + function pad(string: string) { + return whitespace + string + whitespace; + } + + function positive(string: string) { + return `+${string}`; + } + + it(`should pass thru primitive number values`, () => { + const values = [0, 1, NaN]; + + const actual = values.map(toNumber); + + expect(actual).toEqual(values); + }); + + it(`should convert number primitives and objects to numbers`, () => { + const values = [2, 1.2, MAX_SAFE_INTEGER, MAX_INTEGER, Infinity, NaN]; + + const expected = values.map(value => { + const neg = -value; + return [value, value, neg, neg]; + }); + + const actual = values.map(value => [ + toNumber(value), + toNumber(Object(value)), + toNumber(-value), + toNumber(Object(-value)), + ]); + + expect(actual).toEqual(expected); + }); + + it(`should convert string primitives and objects to numbers`, () => { + const transforms = [identity, pad, positive, negative]; + + const values = [ + '10', + '1.234567890', + `${MAX_SAFE_INTEGER}`, + '1e+308', + '1e308', + '1E+308', + '1E308', + '5e-324', + '5E-324', + 'Infinity', + 'NaN', + ]; + + const expected = values.map(value => { + const n = Number(value); + const neg = -n; + return [n, n, n, n, n, n, neg, neg]; + }); + + const actual = values.map(value => + flatMap(transforms, mod => [toNumber(mod(value)), toNumber(Object(mod(value)))]) + ); + + expect(actual).toEqual(expected); + }); + + it(`should convert binary/octal strings to numbers`, () => { + const numbers = [42, 5349, 1715004]; + const transforms = [identity, pad]; + const values = ['0b101010', '0o12345', '0x1a2b3c']; + + const expected = numbers.map(n => [n, n, n, n, n, n, n, n]); + + const actual = values.map(value => { + const upper = value.toUpperCase(); + return flatMap(transforms, mod => [ + toNumber(mod(value)), + toNumber(Object(mod(value))), + toNumber(mod(upper)), + toNumber(Object(mod(upper))), + ]); + }); + + expect(actual).toEqual(expected); + }); + + it(`should convert invalid binary/octal strings to 'NaN'`, () => { + const transforms = [identity, pad, positive, negative]; + const values = ['0b', '0o', '0x', '0b1010102', '0o123458', '0x1a2b3x']; + + const expected = values.map(() => [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN]); + + const actual = values.map(value => + flatMap(transforms, mod => [toNumber(mod(value)), toNumber(Object(mod(value)))]) + ); + + expect(actual).toEqual(expected); + }); + + it(`should convert symbols to 'NaN'`, () => { + const object1 = Object(symbol); + const object2 = Object(symbol); + const values = [symbol, object1, object2]; + const expected = values.map(() => NaN); + + object2.valueOf = undefined; + const actual = values.map(toNumber); + + expect(actual).toEqual(expected); + }); + + it(`should convert empty values to \`0\` or \`NaN\``, () => { + const values = falsey.concat(whitespace); + + const expected = values.map(value => (value !== whitespace ? Number(value) : 0)); + + const actual = values.map((value, index) => (index ? toNumber(value) : toNumber())); + + expect(actual).toEqual(expected); + }); + + it(`should coerce objects to numbers`, () => { + const values: any = [ + {}, + [], + [1], + [1, 2], + { valueOf: '1.1' }, + { valueOf: '1.1', toString: () => '2.2' }, + { valueOf: () => '1.1', toString: '2.2' }, + { + valueOf: () => '1.1', + toString: () => '2.2', + }, + { valueOf: () => '-0x1a2b3c' }, + { toString: () => '-0x1a2b3c' }, + { valueOf: () => '0o12345' }, + { toString: () => '0o12345' }, + { valueOf: () => '0b101010' }, + { toString: () => '0b101010' }, + ]; + + const expected = [NaN, 0, 1, NaN, NaN, 2.2, 1.1, 1.1, NaN, NaN, 5349, 5349, 42, 42]; + + const actual = values.map(toNumber); + + expect(actual).toEqual(expected); + }); +}); diff --git a/src/compat/util/toNumber.ts b/src/compat/util/toNumber.ts new file mode 100644 index 000000000..9d53b5eb8 --- /dev/null +++ b/src/compat/util/toNumber.ts @@ -0,0 +1,25 @@ +import { isSymbol } from '../predicate/isSymbol'; + +/** + * Converts `value` to a number. + * + * Unlike `Number()`, this function returns `NaN` for symbols. + * + * @param {unknown} value - The value to convert. + * @returns {number} Returns the number. + * + * @example + * toNumber(3.2); // => 3.2 + * toNumber(Number.MIN_VALUE); // => 5e-324 + * toNumber(Infinity); // => Infinity + * toNumber('3.2'); // => 3.2 + * toNumber(Symbol.iterator); // => NaN + * toNumber(NaN); // => NaN + */ +export function toNumber(value?: unknown): number { + if (isSymbol(value)) { + return NaN; + } + + return Number(value); +} diff --git a/src/function/after.spec.ts b/src/function/after.spec.ts index 889acac5c..349247c48 100644 --- a/src/function/after.spec.ts +++ b/src/function/after.spec.ts @@ -2,14 +2,14 @@ import { describe, expect, it, vi } from 'vitest'; import { after } from './after'; describe('after', () => { - it('should throw error if n is less than zero.', async () => { + it('should throw error if n is less than zero.', () => { const mockFn = vi.fn(); const n = -1; expect(() => after(n, mockFn)).toThrowErrorMatchingInlineSnapshot('[Error: n must be a non-negative integer.]'); expect(() => after(NaN, mockFn)).toThrowErrorMatchingInlineSnapshot('[Error: n must be a non-negative integer.]'); }); - it('should create a function that invokes `func` only after being called `n` calls.`', async () => { + it('should create a function that invokes `func` only after being called `n` calls.`', () => { const mockFn = vi.fn(); const n = 3; @@ -26,7 +26,7 @@ describe('after', () => { expect(mockFn).toHaveBeenCalledTimes(2); }); - it('should not invoke func immediately when n is zero.', async () => { + it('should not invoke func immediately when n is zero.', () => { const mockFn = vi.fn(); const afterFn = after(0, mockFn); expect(mockFn).toHaveBeenCalledTimes(0); @@ -35,7 +35,7 @@ describe('after', () => { expect(mockFn).toHaveBeenCalledTimes(1); }); - it('should handle arguments correctly.', async () => { + it('should handle arguments correctly.', () => { const mockFn = vi.fn(); mockFn.mockReturnValue(3); diff --git a/src/function/before.spec.ts b/src/function/before.spec.ts index 9ade61b39..db1a0e163 100644 --- a/src/function/before.spec.ts +++ b/src/function/before.spec.ts @@ -2,12 +2,12 @@ import { describe, expect, it, vi } from 'vitest'; import { before } from './before'; describe('before', () => { - it('should throw error if n is less than zero.', async () => { + it('should throw error if n is less than zero.', () => { const mockFn = vi.fn(); expect(() => before(-1, mockFn)).toThrowErrorMatchingInlineSnapshot('[Error: n must be a non-negative integer.]'); }); - it('should create a function that invokes `func` only until the `n-1`-th calls.', async () => { + it('should create a function that invokes `func` only until the `n-1`-th calls.', () => { const mockFn = vi.fn(); mockFn.mockReturnValue(1); const n = 3; @@ -21,7 +21,7 @@ describe('before', () => { expect(beforeFn()).toBeUndefined(); }); - it('should not invoke func immediately when n is a positive integer', async () => { + it('should not invoke func immediately when n is a positive integer', () => { const mockFn = vi.fn(); mockFn.mockReturnValue(1); const n = 3; @@ -32,7 +32,7 @@ describe('before', () => { expect(mockFn).toHaveBeenCalledTimes(1); }); - it('should handle arguments correctly', async () => { + it('should handle arguments correctly', () => { const mockFn = vi.fn(); mockFn.mockReturnValue(3); const n = 3; diff --git a/src/function/curry.ts b/src/function/curry.ts index 9d403a8ae..eb18dd3c8 100644 --- a/src/function/curry.ts +++ b/src/function/curry.ts @@ -51,18 +51,17 @@ export function curry(func: (p1: P1, p2: P2) => R): (p1: P1) => (p2: * Curries a function, allowing it to be called with a single argument at a time and returning a new function that takes the next argument. * This process continues until all arguments have been provided, at which point the original function is called with all accumulated arguments. * - * @param {(p1: P1, p2: P2, p3: P3, p4: P4) => R} func - The function to curry. - * @returns {(p1: P1) => (p2: P2) => (p3: P3) => (p4: P4) => R} A curried function. + * @param {(p1: P1, p2: P2, p3: P3) => R} func - The function to curry. + * @returns {(p1: P1) => (p2: P2) => (p3: P3) => R} A curried function. * * @example - * function fourArgFunc(a: number, b: number, c: number, d: number) { - * return a + b + c + d; + * function threeArgFunc(a: number, b: number, c: number) { + * return a + b + c; * } - * const curriedFourArgFunc = curry(fourArgFunc); - * const add1 = curriedFourArgFunc(1); - * const add2 = add1(2); - * const add3 = add2(3); - * console.log(add3(4)); // 10 + * const curriedThreeArgFunc = curry(threeArgFunc); + * const add1 = curriedThreeArgFunc(1); + * const add3 = add1(2); + * console.log(add3(3)); // 6 */ export function curry(func: (p1: P1, p2: P2, p3: P3) => R): (p1: P1) => (p2: P2) => (p3: P3) => R; @@ -79,9 +78,9 @@ export function curry(func: (p1: P1, p2: P2, p3: P3) => R): (p1: * } * const curriedFourArgFunc = curry(fourArgFunc); * const add1 = curriedFourArgFunc(1); - * const add2 = add1(2); - * const add3 = add2(3); - * console.log(add3(4)); // 10 + * const add3 = add1(2); + * const add6 = add3(3); + * console.log(add6(4)); // 10 */ export function curry( func: (p1: P1, p2: P2, p3: P3, p4: P4) => R @@ -100,10 +99,10 @@ export function curry( * } * const curriedFiveArgFunc = curry(fiveArgFunc); * const add1 = curriedFiveArgFunc(1); - * const add2 = add1(2); - * const add3 = add2(3); - * const add4 = add3(4); - * console.log(add4(5)); // 15 + * const add3 = add1(2); + * const add6 = add3(3); + * const add10 = add6(4); + * console.log(add10(5)); // 15 */ export function curry( func: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R @@ -124,13 +123,13 @@ export function curry( * const curriedSum = curry(sum); * * // The parameter `a` should be given the value `10`. - * const sum10 = curriedSum(10); + * const add10 = curriedSum(10); * * // The parameter `b` should be given the value `15`. - * const sum25 = sum10(15); + * const add25 = add10(15); * * // The parameter `c` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value. - * const result = sum25(5); + * const result = add25(5); */ export function curry(func: (...args: any[]) => any): (...args: any[]) => any; @@ -149,13 +148,13 @@ export function curry(func: (...args: any[]) => any): (...args: any[]) => any; * const curriedSum = curry(sum); * * // The parameter `a` should be given the value `10`. - * const sum10 = curriedSum(10); + * const add10 = curriedSum(10); * * // The parameter `b` should be given the value `15`. - * const sum25 = sum10(15); + * const add25 = add10(15); * * // The parameter `c` should be given the value `5`. The function 'sum' has received all its arguments and will now return a value. - * const result = sum25(5); + * const result = add25(5); */ export function curry(func: (...args: any[]) => any): (...args: any[]) => any { if (func.length === 0 || func.length === 1) { diff --git a/src/function/debounce.spec.ts b/src/function/debounce.spec.ts index ab4d1f7c0..f57da7e15 100644 --- a/src/function/debounce.spec.ts +++ b/src/function/debounce.spec.ts @@ -74,7 +74,7 @@ describe('debounce', () => { expect(func).toHaveBeenCalledTimes(2); }); - it('should have no effect if we call cancel when the function is not executed', async () => { + it('should have no effect if we call cancel when the function is not executed', () => { const func = vi.fn(); const debounceMs = 50; const debouncedFunc = debounce(func, debounceMs); diff --git a/src/function/debounce.ts b/src/function/debounce.ts index ab23af2cc..8ed41ed8f 100644 --- a/src/function/debounce.ts +++ b/src/function/debounce.ts @@ -1,26 +1,3 @@ -interface DebounceTimer { - /** - * Checks if the timer is active. - * @returns {boolean} True if the timer is active, otherwise false. - */ - isActive: () => boolean; - - /** - * Triggers the debounce timer. - * This method resets the timer and schedules the execution of the debounced function - * after the specified delay. If the timer is already active, it clears the existing timeout - * before setting a new one. - */ - trigger: () => void; - - /** - * Cancels any pending execution of the debounced function. - * This method clears the active timer, ensuring that the function will not be called - * at the end of the debounce period. It also resets any stored context or arguments. - */ - cancel: () => void; -} - interface DebounceOptions { /** * An optional AbortSignal to cancel the debounced function. @@ -158,6 +135,7 @@ export function debounce void>( return; } + // eslint-disable-next-line @typescript-eslint/no-this-alias pendingThis = this; pendingArgs = args; diff --git a/src/function/throttle.spec.ts b/src/function/throttle.spec.ts index 229438244..cb1f74da9 100644 --- a/src/function/throttle.spec.ts +++ b/src/function/throttle.spec.ts @@ -3,7 +3,7 @@ import { throttle } from './throttle'; import { delay } from '../promise'; describe('throttle', () => { - it('should throttle function calls', async () => { + it('should throttle function calls', () => { const func = vi.fn(); const throttledFunc = throttle(func, 100); @@ -34,7 +34,7 @@ describe('throttle', () => { expect(func).toHaveBeenCalledTimes(2); }); - it('should call the function with correct arguments', async () => { + it('should call the function with correct arguments', () => { const func = vi.fn(); const throttleMs = 50; const throttledFunc = throttle(func, throttleMs); diff --git a/src/object/omit.ts b/src/object/omit.ts index 0792157c8..b03d0626c 100644 --- a/src/object/omit.ts +++ b/src/object/omit.ts @@ -15,10 +15,11 @@ * const result = omit(obj, ['b', 'c']); * // result will be { a: 1 } */ -export function omit, K extends keyof T>(obj: T, keys: K[]): Omit { +export function omit, K extends keyof T>(obj: T, keys: readonly K[]): Omit { const result = { ...obj }; - for (const key of keys) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; delete result[key]; } diff --git a/src/object/omitBy.ts b/src/object/omitBy.ts index 0c38b4507..997c971b7 100644 --- a/src/object/omitBy.ts +++ b/src/object/omitBy.ts @@ -23,12 +23,12 @@ export function omitBy>( ): Partial { const result: Partial = {}; - for (const [key, value] of Object.entries(obj)) { - if (shouldOmit(value, key)) { - continue; + const objEntries = Object.entries(obj); + for (let i = 0; i < objEntries.length; i++) { + const [key, value] = objEntries[i]; + if (!shouldOmit(value, key)) { + (result as any)[key] = value; } - - (result as any)[key] = value; } return result; diff --git a/src/object/pick.ts b/src/object/pick.ts index f3df75697..fed7407b7 100644 --- a/src/object/pick.ts +++ b/src/object/pick.ts @@ -18,7 +18,8 @@ export function pick, K extends keyof T>(obj: T, keys: readonly K[]): Pick { const result = {} as Pick; - for (const key of keys) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; result[key] = obj[key]; } diff --git a/src/object/pickBy.ts b/src/object/pickBy.ts index 244a26c07..bf4123ae9 100644 --- a/src/object/pickBy.ts +++ b/src/object/pickBy.ts @@ -23,12 +23,12 @@ export function pickBy>( ): Partial { const result: Partial = {}; - for (const [key, value] of Object.entries(obj)) { - if (!shouldPick(value, key)) { - continue; + const objEntries = Object.entries(obj); + for (let i = 0; i < objEntries.length; i++) { + const [key, value] = objEntries[i]; + if (shouldPick(value, key)) { + (result as any)[key] = value; } - - (result as any)[key] = value; } return result; diff --git a/src/string/_internal/getWords.spec.ts b/src/string/_internal/getWords.spec.ts index 6b66640ea..88ab4dfaa 100644 --- a/src/string/_internal/getWords.spec.ts +++ b/src/string/_internal/getWords.spec.ts @@ -2,61 +2,61 @@ import { describe, expect, it } from 'vitest'; import { getWords } from './getWords'; describe('caseSplitPattern', () => { - it('should match camelCase', async () => { + it('should match camelCase', () => { const str = 'camelCase'; const matches = getWords(str); expect(matches).toEqual(['camel', 'Case']); }); - it('should match snake_case', async () => { + it('should match snake_case', () => { const str = 'snake_case'; const matches = getWords(str); expect(matches).toEqual(['snake', 'case']); }); - it('should match kebab-case', async () => { + it('should match kebab-case', () => { const str = 'kebab-case'; const matches = getWords(str); expect(matches).toEqual(['kebab', 'case']); }); - it('should handle mixed formats', async () => { + it('should handle mixed formats', () => { const str = 'camelCase_snake_case-kebabCase'; const matches = getWords(str); expect(matches).toEqual(['camel', 'Case', 'snake', 'case', 'kebab', 'Case']); }); - it('should match acronyms', async () => { + it('should match acronyms', () => { const str = 'HTTPRequest'; const matches = getWords(str); expect(matches).toEqual(['HTTP', 'Request']); }); - it('should match special characters', async () => { + it('should match special characters', () => { const str = 'special_characters@123'; const matches = getWords(str); expect(matches).toEqual(['special', 'characters', '123']); }); - it('should handle leading and trailing whitespace', async () => { + it('should handle leading and trailing whitespace', () => { const str = ' leading_and_trailing_whitespace '; const matches = getWords(str); expect(matches).toEqual(['leading', 'and', 'trailing', 'whitespace']); }); - it('should handle underscores', async () => { + it('should handle underscores', () => { const str = 'underscore_case_example'; const matches = getWords(str); expect(matches).toEqual(['underscore', 'case', 'example']); }); - it('should handle single character words', async () => { + it('should handle single character words', () => { const str = 'aB'; const matches = getWords(str); expect(matches).toEqual(['a', 'B']); }); - it('should work with hyphens ', () => { + it('should work with hyphens', () => { expect(getWords('--FOO-BAR--')).toEqual(['FOO', 'BAR']); }); diff --git a/src/string/camelCase.spec.ts b/src/string/camelCase.spec.ts index 12372d161..c868c2eb6 100644 --- a/src/string/camelCase.spec.ts +++ b/src/string/camelCase.spec.ts @@ -2,39 +2,35 @@ import { describe, expect, it } from 'vitest'; import { camelCase } from './camelCase'; describe('camelCase', () => { - it('should change camel case to camel case', async () => { + it('should change camel case to camel case', () => { expect(camelCase('camelCase')).toEqual('camelCase'); }); - it('should change space to camel case', async () => { + it('should change space to camel case', () => { expect(camelCase('some whitespace')).toEqual('someWhitespace'); }); - it('should change hyphen to camel case', async () => { + it('should change hyphen to camel case', () => { expect(camelCase('hyphen-text')).toEqual('hyphenText'); }); - it('should change Acronyms to small letter', async () => { + it('should change Acronyms to small letter', () => { expect(camelCase('HTTPRequest')).toEqual('httpRequest'); }); - it('should handle leading and trailing whitespace', async () => { - expect(camelCase(' leading and trailing whitespace')).toEqual('leadingAndTrailingWhitespace'); + it('should handle leading and trailing whitespace', () => { + expect(camelCase(' leading and trailing whitespace ')).toEqual('leadingAndTrailingWhitespace'); }); - it('should handle special characters correctly', async () => { + it('should handle special characters correctly', () => { expect(camelCase('special@characters!')).toEqual('specialCharacters'); }); - it('should handle strings that are already in camel_case', async () => { - expect(camelCase('camel_case')).toEqual('camelCase'); - }); - - it('should work with an empty string', async () => { + it('should work with an empty string', () => { expect(camelCase('')).toEqual(''); }); - it('should work with screaming camel case', async () => { + it('should work with screaming camel case', () => { expect(camelCase('FOO_BAR')).toEqual('fooBar'); }); }); diff --git a/src/string/capitalize.spec.ts b/src/string/capitalize.spec.ts index 8edbf498d..a0c4947d8 100644 --- a/src/string/capitalize.spec.ts +++ b/src/string/capitalize.spec.ts @@ -2,31 +2,31 @@ import { describe, it, expect } from 'vitest'; import { capitalize } from './capitalize'; describe('capitalize', () => { - it('should converts the first character of string to upper case', async () => { + it('should converts the first character of string to upper case', () => { expect(capitalize('fred')).toEqual('Fred'); }); - it('should converts the first character of string to upper case and the remaining to lower case.', async () => { + it('should converts the first character of string to upper case and the remaining to lower case.', () => { expect(capitalize('FRED')).toEqual('Fred'); }); - it('should handle special characters correctly', async () => { + it('should handle special characters correctly', () => { expect(capitalize('special@characters!')).toEqual('Special@characters!'); }); - it('should handle hyphen correctly', async () => { + it('should handle hyphen correctly', () => { expect(capitalize('hyphen-text')).toEqual('Hyphen-text'); }); - it('should handle leading whitespace', async () => { + it('should handle leading whitespace', () => { expect(capitalize(' fred')).toEqual(' fred'); }); - it('should handle strings that are already in capitalize', async () => { + it('should handle strings that are already in capitalize', () => { expect(capitalize('Fred')).toEqual('Fred'); }); - it('should work with an empty string', async () => { + it('should work with an empty string', () => { expect(capitalize('')).toEqual(''); }); }); diff --git a/src/string/constantCase.spec.ts b/src/string/constantCase.spec.ts new file mode 100644 index 000000000..d07a2a492 --- /dev/null +++ b/src/string/constantCase.spec.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest'; +import { constantCase } from './constantCase'; + +describe('constantCase', () => { + it('should change camel case to constant case', async () => { + expect(constantCase('camelCase')).toEqual('CAMEL_CASE'); + }); + + it('should change space to underscore', async () => { + expect(constantCase('some whitespace')).toEqual('SOME_WHITESPACE'); + }); + + it('should change hyphen to underscore', async () => { + expect(constantCase('hyphen-text')).toEqual('HYPHEN_TEXT'); + }); + + it('should change Acronyms to small letter', async () => { + expect(constantCase('HTTPRequest')).toEqual('HTTP_REQUEST'); + }); + + it('should handle leading and trailing whitespace', async () => { + expect(constantCase(' leading and trailing whitespace')).toEqual('LEADING_AND_TRAILING_WHITESPACE'); + }); + + it('should handle special characters correctly', async () => { + expect(constantCase('special@characters!')).toEqual('SPECIAL_CHARACTERS'); + }); + + it('should handle strings that are already in CONSTANT_CASE', async () => { + expect(constantCase('CONSTANT_CASE')).toEqual('CONSTANT_CASE'); + }); + + it('should work with an empty string', async () => { + expect(constantCase('')).toEqual(''); + }); + + it('should work with screaming constant case', async () => { + expect(constantCase('foo_bar')).toEqual('FOO_BAR'); + }); +}); diff --git a/src/string/constantCase.ts b/src/string/constantCase.ts new file mode 100644 index 000000000..b399da1ac --- /dev/null +++ b/src/string/constantCase.ts @@ -0,0 +1,21 @@ +import { getWords } from './_internal/getWords.ts'; + +/** + * Converts a string to constant case. + * + * Constant case is a naming convention where each word is written in uppercase letters and separated by an underscore (`_`). For example, `CONSTANT_CASE`. + * + * @param {string} str - The string that is to be changed to constant case. + * @returns {string} - The converted string to constant case. + * + * @example + * const convertedStr1 = constantCase('camelCase') // returns 'CAMEL_CASE' + * const convertedStr2 = constantCase('some whitespace') // returns 'SOME_WHITESPACE' + * const convertedStr3 = constantCase('hyphen-text') // returns 'HYPHEN_TEXT' + * const convertedStr4 = constantCase('HTTPRequest') // returns 'HTTP_REQUEST' + */ + +export function constantCase(str: string): string { + const words = getWords(str); + return words.map(word => word.toUpperCase()).join('_'); +} diff --git a/src/string/index.ts b/src/string/index.ts index 841d53531..70d61a89e 100644 --- a/src/string/index.ts +++ b/src/string/index.ts @@ -6,6 +6,7 @@ export { lowerCase } from './lowerCase.ts'; export { startCase } from './startCase.ts'; export { capitalize } from './capitalize.ts'; export { pascalCase } from './pascalCase.ts'; +export { constantCase } from './constantCase.ts'; export { trim } from './trim.ts'; export { trimStart } from './trimStart.ts'; export { trimEnd } from './trimEnd.ts'; diff --git a/src/string/kebabCase.spec.ts b/src/string/kebabCase.spec.ts index 91d30513b..a5b74fb0f 100644 --- a/src/string/kebabCase.spec.ts +++ b/src/string/kebabCase.spec.ts @@ -2,47 +2,47 @@ import { describe, it, expect } from 'vitest'; import { kebabCase } from './kebabCase'; describe('kebabCase', () => { - it('should change camel case to kebab case', async () => { + it('should change camel case to kebab case', () => { expect(kebabCase('camelCase')).toEqual('camel-case'); }); - it('should change space to dash', async () => { + it('should change space to dash', () => { expect(kebabCase('some whitespace')).toEqual('some-whitespace'); }); - it('should change hyphen to dash', async () => { + it('should change hyphen to dash', () => { expect(kebabCase('hyphen-text')).toEqual('hyphen-text'); }); - it('should change Acronyms to small letter', async () => { + it('should change Acronyms to small letter', () => { expect(kebabCase('HTTPRequest')).toEqual('http-request'); }); - it('should handle leading and trailing whitespace', async () => { - expect(kebabCase(' leading and trailing whitespace')).toEqual('leading-and-trailing-whitespace'); + it('should handle leading and trailing whitespace', () => { + expect(kebabCase(' leading and trailing whitespace ')).toEqual('leading-and-trailing-whitespace'); }); - it('should handle special characters correctly', async () => { + it('should handle special characters correctly', () => { expect(kebabCase('special@characters!')).toEqual('special-characters'); }); - it('should handle strings that are already in snake_case', async () => { + it('should handle strings that are already in snake_case', () => { expect(kebabCase('snake_case')).toEqual('snake-case'); }); - it('should work with an empty string', async () => { + it('should work with an empty string', () => { expect(kebabCase('')).toEqual(''); }); - it('should work with an leading and trailing underscores', async () => { + it('should work with an leading and trailing underscores', () => { expect(kebabCase('__foo_bar___')).toEqual('foo-bar'); }); - it('should work with screaming snake case', async () => { + it('should work with screaming snake case', () => { expect(kebabCase('FOO_BAR')).toEqual('foo-bar'); }); - it('should work with capitalized words', async () => { + it('should work with capitalized words', () => { expect(kebabCase('Foo Bar')).toEqual('foo-bar'); }); }); diff --git a/src/string/lowerCase.spec.ts b/src/string/lowerCase.spec.ts index 1f3ed3642..4970fe9d5 100644 --- a/src/string/lowerCase.spec.ts +++ b/src/string/lowerCase.spec.ts @@ -2,39 +2,39 @@ import { describe, it, expect } from 'vitest'; import { lowerCase } from './lowerCase'; describe('lowerCase', () => { - it('should change camel case to lower case', async () => { + it('should change camel case to lower case', () => { expect(lowerCase('camelCase')).toEqual('camel case'); }); - it('should change space to space', async () => { + it('should change space to space', () => { expect(lowerCase('some whitespace')).toEqual('some whitespace'); }); - it('should change hyphen to space', async () => { + it('should change hyphen to space', () => { expect(lowerCase('hyphen-text')).toEqual('hyphen text'); }); - it('should change Acronyms to small letter', async () => { + it('should change Acronyms to small letter', () => { expect(lowerCase('HTTPRequest')).toEqual('http request'); }); - it('should handle leading and trailing whitespace', async () => { - expect(lowerCase(' leading and trailing whitespace')).toEqual('leading and trailing whitespace'); + it('should handle leading and trailing whitespace', () => { + expect(lowerCase(' leading and trailing whitespace ')).toEqual('leading and trailing whitespace'); }); - it('should handle special characters correctly', async () => { + it('should handle special characters correctly', () => { expect(lowerCase('special@characters!')).toEqual('special characters'); }); - it('should handle strings that are already in lower case', async () => { + it('should handle strings that are already in lower case', () => { expect(lowerCase('lower_case')).toEqual('lower case'); }); - it('should work with an empty string', async () => { + it('should work with an empty string', () => { expect(lowerCase('')).toEqual(''); }); - it('should work with screaming snake case', async () => { + it('should work with screaming snake case', () => { expect(lowerCase('FOO_BAR')).toEqual('foo bar'); }); }); diff --git a/src/string/pascalCase.spec.ts b/src/string/pascalCase.spec.ts index 90897ed94..5fb29b542 100644 --- a/src/string/pascalCase.spec.ts +++ b/src/string/pascalCase.spec.ts @@ -15,7 +15,7 @@ describe('PascalCase', () => { }); it('should handle leading and trailing whitespace', () => { - expect(pascalCase(' leading and trailing whitespace')).toEqual('LeadingAndTrailingWhitespace'); + expect(pascalCase(' leading and trailing whitespace ')).toEqual('LeadingAndTrailingWhitespace'); }); it('should handle special characters correctly', () => { diff --git a/src/string/snakeCase.spec.ts b/src/string/snakeCase.spec.ts index aabbed08c..c56a57831 100644 --- a/src/string/snakeCase.spec.ts +++ b/src/string/snakeCase.spec.ts @@ -2,39 +2,39 @@ import { describe, it, expect } from 'vitest'; import { snakeCase } from './snakeCase'; describe('snakeCase', () => { - it('should change camel case to snake case', async () => { + it('should change camel case to snake case', () => { expect(snakeCase('camelCase')).toEqual('camel_case'); }); - it('should change space to underscore', async () => { + it('should change space to underscore', () => { expect(snakeCase('some whitespace')).toEqual('some_whitespace'); }); - it('should change hyphen to underscore', async () => { + it('should change hyphen to underscore', () => { expect(snakeCase('hyphen-text')).toEqual('hyphen_text'); }); - it('should change Acronyms to small letter', async () => { + it('should change Acronyms to small letter', () => { expect(snakeCase('HTTPRequest')).toEqual('http_request'); }); - it('should handle leading and trailing whitespace', async () => { - expect(snakeCase(' leading and trailing whitespace')).toEqual('leading_and_trailing_whitespace'); + it('should handle leading and trailing whitespace', () => { + expect(snakeCase(' leading and trailing whitespace ')).toEqual('leading_and_trailing_whitespace'); }); - it('should handle special characters correctly', async () => { + it('should handle special characters correctly', () => { expect(snakeCase('special@characters!')).toEqual('special_characters'); }); - it('should handle strings that are already in snake_case', async () => { + it('should handle strings that are already in snake_case', () => { expect(snakeCase('snake_case')).toEqual('snake_case'); }); - it('should work with an empty string', async () => { + it('should work with an empty string', () => { expect(snakeCase('')).toEqual(''); }); - it('should work with screaming snake case', async () => { + it('should work with screaming snake case', () => { expect(snakeCase('FOO_BAR')).toEqual('foo_bar'); }); }); diff --git a/src/string/startCase.spec.ts b/src/string/startCase.spec.ts index fe0e5acb2..74ec3b823 100644 --- a/src/string/startCase.spec.ts +++ b/src/string/startCase.spec.ts @@ -5,20 +5,20 @@ describe('startCase', function () { it('should capitalize each word', function () { expect(startCase('--foo-bar--')).toBe('Foo Bar'); expect(startCase('fooBar')).toBe('Foo Bar'); - expect(startCase('__FOO_BAR__')).toBe('FOO BAR'); + expect(startCase('__FOO_BAR__')).toBe('Foo Bar'); }); it('should handle compound words', function () { expect(startCase('createElement')).toBe('Create Element'); expect(startCase('iPhone')).toBe('I Phone'); - expect(startCase('XMLHttpRequest')).toBe('XML Http Request'); + expect(startCase('XMLHttpRequest')).toBe('Xml Http Request'); }); it('should handle various delimiters', function () { expect(startCase('_abc_123_def')).toBe('Abc 123 Def'); expect(startCase('__abc__123__def__')).toBe('Abc 123 Def'); - expect(startCase('ABC-DEF')).toBe('ABC DEF'); - expect(startCase('ABC DEF')).toBe('ABC DEF'); + expect(startCase('ABC-DEF')).toBe('Abc Def'); + expect(startCase('ABC DEF')).toBe('Abc Def'); }); it('should handle empty strings', function () { @@ -30,16 +30,16 @@ describe('startCase', function () { }); it('should work with numbers', function () { - expect(startCase('12abc 12ABC')).toBe('12 Abc 12 ABC'); + expect(startCase('12abc 12ABC')).toBe('12 Abc 12 Abc'); }); it('should handle consecutive uppercase letters', function () { - expect(startCase('ABC')).toBe('ABC'); - expect(startCase('ABCdef')).toBe('AB Cdef'); + expect(startCase('ABC')).toBe('Abc'); + expect(startCase('ABCdef')).toBe('Ab Cdef'); }); it('should handle combinations of numbers and letters', function () { - expect(startCase('123ABC')).toBe('123 ABC'); + expect(startCase('123ABC')).toBe('123 Abc'); expect(startCase('a1B2c3')).toBe('A 1 B 2 C 3'); }); @@ -58,4 +58,9 @@ describe('startCase', function () { expect(startCase(' foo bar ')).toBe('Foo Bar'); expect(startCase('\tfoo\nbar')).toBe('Foo Bar'); }); + + it('should convert the non-first characters to lowercase', function () { + expect(startCase('FOO BAR')).toBe('Foo Bar'); + expect(startCase('FOO BAR BAZ')).toBe('Foo Bar Baz'); + }); }); diff --git a/src/string/startCase.ts b/src/string/startCase.ts index a8af0fae4..66879ce18 100644 --- a/src/string/startCase.ts +++ b/src/string/startCase.ts @@ -9,22 +9,20 @@ import { getWords } from './_internal/getWords.ts'; * * @example * const result1 = startCase('hello world'); // result will be 'Hello World' - * const result2 = startCase('HELLO WORLD'); // result will be 'HELLO WORLD' + * const result2 = startCase('HELLO WORLD'); // result will be 'Hello World' * const result3 = startCase('hello-world'); // result will be 'Hello World' * const result4 = startCase('hello_world'); // result will be 'Hello World' */ export function startCase(str: string): string { const words = getWords(str.trim()); let result = ''; - for (const word of words) { + for (let i = 0; i < words.length; i++) { + const word = words[i]; if (result) { result += ' '; } - if (word === word.toUpperCase()) { - result += word; - } else { - result += word[0].toUpperCase() + word.slice(1).toLowerCase(); - } + + result += word[0].toUpperCase() + word.slice(1).toLowerCase(); } return result; } diff --git a/src/string/trim.spec.ts b/src/string/trim.spec.ts index 2f0277e7d..730269af8 100644 --- a/src/string/trim.spec.ts +++ b/src/string/trim.spec.ts @@ -2,47 +2,47 @@ import { describe, it, expect } from 'vitest'; import { trim } from './trim.ts'; describe('trim', () => { - it('should return the string without the double quotes', async () => { + it('should return the string without the double quotes', () => { expect(trim('"hello, world!"', '"')).toEqual('hello, world!'); }); - it('should return the string without special characters', async () => { + it('should return the string without special characters', () => { expect(trim('!@#$%^&*wow%#$', ['!', '@', '#', '$', '%', '^', '&', '*'])).toEqual('wow'); }); - it('should return the string unchanged when no matching characters are found', async () => { + it('should return the string unchanged when no matching characters are found', () => { expect(trim('hello', 'x')).toEqual('hello'); }); - it('should remove all occurrences of a single character', async () => { + it('should remove all occurrences of a single character', () => { expect(trim('banana', 'a')).toEqual('banan'); }); - it('should remove all occurrences of multiple characters', async () => { + it('should remove all occurrences of multiple characters', () => { expect(trim('abracadabra', ['a', 'b'])).toEqual('racadabr'); }); - it('should handle an empty string', async () => { + it('should handle an empty string', () => { expect(trim('', 'a')).toEqual(''); }); - it('should remove spaces when specified', async () => { + it('should remove spaces when specified', () => { expect(trim('hello world', ' ')).toEqual('hello world'); }); - it('should handle a case where the string is already trimmed', async () => { + it('should handle a case where the string is already trimmed', () => { expect(trim('alreadyTrimmed', 'x')).toEqual('alreadyTrimmed'); }); - it('should return an empty string when all characters are removed', async () => { + it('should return an empty string when all characters are removed', () => { expect(trim('aaaaa', 'a')).toEqual(''); }); - it('should remove numbers from a string', async () => { + it('should remove numbers from a string', () => { expect(trim('123abc456', ['1', '2', '3', '4', '5', '6'])).toEqual('abc'); }); - it('should trim the string without giving the second parameter, which defaults to whitespace', async () => { + it('should trim the string without giving the second parameter, which defaults to whitespace', () => { expect(trim(' hello world ')).toEqual('hello world'); }); }); diff --git a/src/string/trimEnd.spec.ts b/src/string/trimEnd.spec.ts index b8b6884bb..fb9d9e6ff 100644 --- a/src/string/trimEnd.spec.ts +++ b/src/string/trimEnd.spec.ts @@ -2,47 +2,47 @@ import { describe, expect, it } from 'vitest'; import { trimEnd } from './trimEnd.ts'; describe('trimEnd', () => { - it('should remove trailing characters from the string', async () => { + it('should remove trailing characters from the string', () => { expect(trimEnd('hello---', '-')).toEqual('hello'); }); - it('should remove trailing characters when multiple characters are provided', async () => { + it('should remove trailing characters when multiple characters are provided', () => { expect(trimEnd('123000', '0')).toEqual('123'); }); - it('should return the string unchanged when there are no trailing characters to remove', async () => { + it('should return the string unchanged when there are no trailing characters to remove', () => { expect(trimEnd('hello', 'x')).toEqual('hello'); }); - it('should remove trailing occurrences of a single character', async () => { + it('should remove trailing occurrences of a single character', () => { expect(trimEnd('abcabcabc', 'c')).toEqual('abcabcab'); }); - it('should handle an empty string', async () => { + it('should handle an empty string', () => { expect(trimEnd('', 'x')).toEqual(''); }); - it('should remove trailing spaces when specified', async () => { + it('should remove trailing spaces when specified', () => { expect(trimEnd('hello world ', ' ')).toEqual('hello world'); }); - it('should handle a case where the string is already trimmed', async () => { + it('should handle a case where the string is already trimmed', () => { expect(trimEnd('trimmed', 'x')).toEqual('trimmed'); }); - it('should return an empty string when all characters are removed', async () => { + it('should return an empty string when all characters are removed', () => { expect(trimEnd('xxxxx', 'x')).toEqual(''); }); - it('should remove numbers from the end of a string', async () => { + it('should remove numbers from the end of a string', () => { expect(trimEnd('abc123456', '6')).toEqual('abc12345'); }); - it('should handle cases where multiple trailing characters need removal', async () => { + it('should handle cases where multiple trailing characters need removal', () => { expect(trimEnd('abc123abc123abc', 'c')).toEqual('abc123abc123ab'); }); - it('should trim the string without giving the second parameter, which defaults to whitespace', async () => { + it('should trim the string without giving the second parameter, which defaults to whitespace', () => { expect(trimEnd(' hello world ')).toEqual(' hello world'); }); }); diff --git a/src/string/trimStart.spec.ts b/src/string/trimStart.spec.ts index 49bb73de4..5e896a0e3 100644 --- a/src/string/trimStart.spec.ts +++ b/src/string/trimStart.spec.ts @@ -2,47 +2,47 @@ import { describe, expect, it } from 'vitest'; import { trimStart } from './trimStart.ts'; describe('trimStart', () => { - it('should remove leading characters from the string', async () => { + it('should remove leading characters from the string', () => { expect(trimStart('---hello', '-')).toEqual('hello'); }); - it('should remove leading zeros from the string', async () => { + it('should remove leading zeros from the string', () => { expect(trimStart('000123', '0')).toEqual('123'); }); - it('should return the string unchanged when there are no leading characters to remove', async () => { + it('should return the string unchanged when there are no leading characters to remove', () => { expect(trimStart('hello', 'x')).toEqual('hello'); }); - it('should remove leading occurrences of a single character', async () => { + it('should remove leading occurrences of a single character', () => { expect(trimStart('abcabcabc', 'a')).toEqual('bcabcabc'); }); - it('should handle an empty string', async () => { + it('should handle an empty string', () => { expect(trimStart('', 'x')).toEqual(''); }); - it('should remove leading spaces when specified', async () => { + it('should remove leading spaces when specified', () => { expect(trimStart(' hello world', ' ')).toEqual('hello world'); }); - it('should handle a case where the string is already trimmed', async () => { + it('should handle a case where the string is already trimmed', () => { expect(trimStart('trimmed', 'x')).toEqual('trimmed'); }); - it('should return an empty string when all characters are removed', async () => { + it('should return an empty string when all characters are removed', () => { expect(trimStart('xxxxx', 'x')).toEqual(''); }); - it('should remove numbers from the start of a string', async () => { + it('should remove numbers from the start of a string', () => { expect(trimStart('123456abc', '1')).toEqual('23456abc'); }); - it('should handle cases where multiple leading characters need removal', async () => { + it('should handle cases where multiple leading characters need removal', () => { expect(trimStart('aaaabbbcccc', 'a')).toEqual('bbbcccc'); }); - it('should trim the string without giving the second parameter, which defaults to whitespace', async () => { + it('should trim the string without giving the second parameter, which defaults to whitespace', () => { expect(trimStart(' hello world ')).toEqual('hello world '); }); }); diff --git a/src/string/upperCase.spec.ts b/src/string/upperCase.spec.ts index 0234e85ab..94b797045 100644 --- a/src/string/upperCase.spec.ts +++ b/src/string/upperCase.spec.ts @@ -2,39 +2,39 @@ import { describe, it, expect } from 'vitest'; import { upperCase } from './upperCase'; describe('upperCase', () => { - it('should change camel case to upper case', async () => { + it('should change camel case to upper case', () => { expect(upperCase('camelCase')).toEqual('CAMEL CASE'); }); - it('should change space to space', async () => { + it('should change space to space', () => { expect(upperCase('some whitespace')).toEqual('SOME WHITESPACE'); }); - it('should change hyphen to space', async () => { + it('should change hyphen to space', () => { expect(upperCase('hyphen-text')).toEqual('HYPHEN TEXT'); }); - it('should change Acronyms to small letter', async () => { + it('should change Acronyms to small letter', () => { expect(upperCase('HTTPRequest')).toEqual('HTTP REQUEST'); }); - it('should handle leading and trailing whitespace', async () => { - expect(upperCase(' leading and trailing whitespace')).toEqual('LEADING AND TRAILING WHITESPACE'); + it('should handle leading and trailing whitespace', () => { + expect(upperCase(' leading and trailing whitespace ')).toEqual('LEADING AND TRAILING WHITESPACE'); }); - it('should handle special characters correctly', async () => { + it('should handle special characters correctly', () => { expect(upperCase('special@characters!')).toEqual('SPECIAL CHARACTERS'); }); - it('should handle strings that are already in upper case', async () => { + it('should handle strings that are already in upper case', () => { expect(upperCase('upper_case')).toEqual('UPPER CASE'); }); - it('should work with an empty string', async () => { + it('should work with an empty string', () => { expect(upperCase('')).toEqual(''); }); - it('should work with screaming snake case', async () => { + it('should work with screaming snake case', () => { expect(upperCase('FOO_BAR')).toEqual('FOO BAR'); }); }); diff --git a/yarn.lock b/yarn.lock index 28a109a64..7c94c296a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2851,6 +2851,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.6.0": + version: 8.6.0 + resolution: "@typescript-eslint/scope-manager@npm:8.6.0" + dependencies: + "@typescript-eslint/types": "npm:8.6.0" + "@typescript-eslint/visitor-keys": "npm:8.6.0" + checksum: 10c0/37092ef70171c06854ac67ebfb2255063890c1c6133654e6b15b6adb6d2ab83de4feafd1599f4d02ed71a018226fcb3a389021758ec045e1904fb1798e90b4fe + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/type-utils@npm:8.1.0" @@ -2873,6 +2883,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.6.0": + version: 8.6.0 + resolution: "@typescript-eslint/types@npm:8.6.0" + checksum: 10c0/e7051d212252f7d1905b5527b211e335db4ec5bb1d3a52d73c8d2de6ddf5cbc981f2c92ca9ffcef35f7447bda635ea1ccce5f884ade7f243d14f2a254982c698 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/typescript-estree@npm:8.1.0" @@ -2892,6 +2909,25 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:8.6.0": + version: 8.6.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.6.0" + dependencies: + "@typescript-eslint/types": "npm:8.6.0" + "@typescript-eslint/visitor-keys": "npm:8.6.0" + debug: "npm:^4.3.4" + fast-glob: "npm:^3.3.2" + is-glob: "npm:^4.0.3" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + ts-api-utils: "npm:^1.3.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/33ab8c03221a797865301f09d1d198c67f8b0e3dbf0d13e41f699dc2740242303a9fcfd7b38302cef318541fdedd832fd6e8ba34a5041a57e9114fa134045385 + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/utils@npm:8.1.0" @@ -2906,6 +2942,20 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^8.6.0": + version: 8.6.0 + resolution: "@typescript-eslint/utils@npm:8.6.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.4.0" + "@typescript-eslint/scope-manager": "npm:8.6.0" + "@typescript-eslint/types": "npm:8.6.0" + "@typescript-eslint/typescript-estree": "npm:8.6.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + checksum: 10c0/5b615106342dfdf09f5a73e2554cc0c4d979c262a9a4548eb76ec7045768e0ff0bf0316cf8a5eb5404689cd476fcd335fc84f90eb985557559e42aeee33d687e + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:8.1.0": version: 8.1.0 resolution: "@typescript-eslint/visitor-keys@npm:8.1.0" @@ -2916,6 +2966,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.6.0": + version: 8.6.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.6.0" + dependencies: + "@typescript-eslint/types": "npm:8.6.0" + eslint-visitor-keys: "npm:^3.4.3" + checksum: 10c0/9bd5d5daee9de7e009fdd1b64b1eca685a699d1b2639373bc279c97e25e769fff56fffef708ef66a2b19bc8bb201d36daf9e7084f0e0872178bfcf9d923b41f3 + languageName: node + linkType: hard + "@vitejs/plugin-vue@npm:^5.0.5": version: 5.1.2 resolution: "@vitejs/plugin-vue@npm:5.1.2" @@ -4927,6 +4987,7 @@ __metadata: eslint: "npm:^9.9.0" eslint-config-prettier: "npm:^9.1.0" eslint-plugin-jsdoc: "npm:^50.2.2" + eslint-plugin-no-for-of-array: "npm:^0.0.1" eslint-plugin-vue: "npm:^9.28.0" execa: "npm:^9.3.0" globals: "npm:^15.9.0" @@ -5336,6 +5397,19 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-no-for-of-array@npm:^0.0.1": + version: 0.0.1 + resolution: "eslint-plugin-no-for-of-array@npm:0.0.1" + dependencies: + "@typescript-eslint/utils": "npm:^8.6.0" + peerDependencies: + "@typescript-eslint/parser": ^8.6.0 + eslint: ^9.11.0 + typescript: ^5.6.2 + checksum: 10c0/37aeb5fcd71b05a5cc3c10617febc74c7da51467f1149d2e9f1b9d322b7e5090e780f8efa79007979bb52a026b8212690af74e88b35745dbc9ab3f5f6c0f14d7 + languageName: node + linkType: hard + "eslint-plugin-vue@npm:^9.28.0": version: 9.28.0 resolution: "eslint-plugin-vue@npm:9.28.0"