Repository: remeda/remeda
Pull request #591 (2024-03-23 16:08:40)

fix: Modernize code via lint fixes
Fixing lint and todos now that the target is modern. This includes adding bigint in some places, and other minor tweaks.

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/591/builds/486892) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 700ebdbf1e765b6fd8f70f1660333327a87ae952:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-6xlck2)| Configuration |


Patch:
diff --git a/eslint.config.js b/eslint.config.js
index 0dd89f2..69f1eff 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -39,7 +39,6 @@ export default config(
 
       // This isn't very useful in a utility library, a lot of utilities need to
       // access arrays in a random-access way.
-      // TODO: Once we bump our typescript `target` we should enable this rule again, go over all the non-null-assertions, and see which ones are due to a for loop which could use `Array.prototype.entries` instead.
       "@typescript-eslint/no-non-null-assertion": "off",
 
       // When our return type is just `undefined` (like `first([])`) this rule
@@ -50,20 +49,6 @@ export default config(
       // now instead...
       "@typescript-eslint/no-confusing-void-expression": "off",
 
-      // TODO: Once we bump our typescript `target` we should enable this rule
-      "prefer-object-has-own": "off",
-
-      // TODO: Once we bump our minimum Typescript version above 4.5 we need to change the linting to prefer the inline style which allows us to combine type imports and regular ones:
-      "no-duplicate-imports": "off",
-      "@typescript-eslint/consistent-type-imports": ["warn"],
-
-      // TODO: Once we bump our typescript `target` we should enable these rules.
-      "unicorn/no-for-loop": "off",
-      "unicorn/prefer-at": "off",
-      "unicorn/prefer-number-properties": "off",
-      "unicorn/prefer-spread": "off",
-      "unicorn/prefer-includes": "off",
-
       // TODO: These rules allow us to really standardize our codebase, but they
       // also do sweeping changes to the whole codebase which is very noisy. We
       // should do it in one sweep sometime in the future.
@@ -92,6 +77,7 @@ export default config(
       "no-template-curly-in-string": "warn",
       "no-unmodified-loop-condition": "error",
       "no-unreachable-loop": "error",
+      "no-duplicate-imports": "error",
       "require-atomic-updates": "error",
 
       // Suggestions
@@ -274,6 +260,11 @@ export default config(
       // "strict" extension this rule is defined the opposite way though(?!).
       "@typescript-eslint/consistent-type-definitions": ["warn", "type"],
 
+      "@typescript-eslint/consistent-type-imports": [
+        "warn",
+        { fixStyle: "inline-type-imports" },
+      ],
+
       "@typescript-eslint/no-unused-vars": [
         "error",
         {
@@ -398,7 +389,6 @@ export default config(
   {
     files: ["src/**/*.test.ts"],
     rules: {
-      "unicorn/no-array-callback-reference": "off",
       "unicorn/no-null": "off",
       "unicorn/no-useless-undefined": [
         "warn",
@@ -406,15 +396,6 @@ export default config(
       ],
     },
   },
-  {
-    files: ["test/**/*.ts"],
-    rules: {
-      "@typescript-eslint/explicit-function-return-type": "off",
-      "@typescript-eslint/explicit-module-boundary-types": "off",
-      "unicorn/filename-case": "off",
-      "unicorn/no-null": "off",
-    },
-  },
   {
     files: ["src/_*.ts"],
     rules: {
diff --git a/src/_purryOrderRules.ts b/src/_purryOrderRules.ts
index 90fbd18..d097f5c 100644
--- a/src/_purryOrderRules.ts
+++ b/src/_purryOrderRules.ts
@@ -46,7 +46,7 @@ type Comparable =
 
 //  Notice that `boolean` is special in that it is coerced as a number (0 for
 // `false`, 1 for `true`) implicitly.
-type ComparablePrimitive = boolean | number | string;
+type ComparablePrimitive = bigint | boolean | number | string;
 
 /**
  * Allows functions that want to handle a variadic number of order rules a
diff --git a/src/_quickSelect.ts b/src/_quickSelect.ts
index 7643f19..0e7c0b9 100644
--- a/src/_quickSelect.ts
+++ b/src/_quickSelect.ts
@@ -29,7 +29,7 @@ export const quickSelect = <T>(
       undefined
     : quickSelectImplementation(
         // We need to clone the array because quickSelect mutates it in-place.
-        data.slice(),
+        [...data],
         0 /* left */,
         data.length - 1 /* right */,
         index,
diff --git a/src/_reduceLazy.ts b/src/_reduceLazy.ts
index 458c763..df98e0f 100644
--- a/src/_reduceLazy.ts
+++ b/src/_reduceLazy.ts
@@ -7,8 +7,7 @@ export function _reduceLazy<T, K>(
   const out: Array<K> = [];
 
   // We intentionally use a for loop here instead of reduce for performance reasons. See https://leanylabs.com/blog/js-forEach-map-reduce-vs-for-for_of/ for more info
-  for (let index = 0; index < array.length; index++) {
-    const item = array[index]!;
+  for (const [index, item] of array.entries()) {
     const result = lazy(item, index, array);
     if (result.hasMany === true) {
       out.push(...result.next);
diff --git a/src/concat.ts b/src/concat.ts
index c8842e5..c409d62 100644
--- a/src/concat.ts
+++ b/src/concat.ts
@@ -1,26 +1,35 @@
 import { purry } from "./purry";
 
 /**
- * Combines two arrays.
+ * Merge two or more arrays. This method does not change the existing arrays,
+ * but instead returns a new array, even if the other array is empty.
  *
- * @param arr1 - The first array.
- * @param arr2 - The second array.
+ * @param data - The first items, these would be at the beginning of the new
+ * array.
+ * @param other - The remaining items, these would be at the end of the new
+ * array.
+ * @returns A new array with the items of the first array followed by the items
+ * of the second array.
  * @signature
- *    R.concat(arr1, arr2);
+ *    R.concat(data, other);
  * @example
  *    R.concat([1, 2, 3], ['a']) // [1, 2, 3, 'a']
  * @dataFirst
  * @category Array
  */
-export function concat<T, K>(
-  arr1: ReadonlyArray<T>,
-  arr2: ReadonlyArray<K>,
-): Array<K | T>;
+export function concat<T1, T2>(
+  data: ReadonlyArray<T1>,
+  other: ReadonlyArray<T2>,
+): [...Array<T1>, ...Array<T2>];
 
 /**
- * Combines two arrays.
+ * Merge two or more arrays. This method does not change the existing arrays,
+ * but instead returns a new array, even if the other array is empty.
  *
- * @param arr2 - The second array.
+ * @param other - The remaining items, these would be at the end of the new
+ * array.
+ * @returns A new array with the items of the first array followed by the items
+ * of the second array.
  * @signature
  *    R.concat(arr2)(arr1);
  * @example
@@ -29,16 +38,14 @@ export function concat<T, K>(
  * @category Array
  */
 export function concat<T, K>(
-  arr2: ReadonlyArray<K>,
-): (arr1: ReadonlyArray<T>) => Array<K | T>;
+  other: ReadonlyArray<K>,
+): (data: ReadonlyArray<T>) => [...Array<T>, ...Array<K>];
 
 export function concat(...args: ReadonlyArray<unknown>): unknown {
-  return purry(_concat, args);
+  return purry(concatImplementation, args);
 }
 
-function _concat<T, K>(
+const concatImplementation = <T, K>(
   arr1: ReadonlyArray<T>,
   arr2: ReadonlyArray<K>,
-): Array<K | T> {
-  return (arr1 as Array<K | T>).concat(arr2);
-}
+): [...Array<T>, ...Array<K>] => [...arr1, ...arr2];
diff --git a/src/dropFirstBy.ts b/src/dropFirstBy.ts
index bc262da..23bd977 100644
--- a/src/dropFirstBy.ts
+++ b/src/dropFirstBy.ts
@@ -1,6 +1,8 @@
 import { heapify, heapMaybeInsert } from "./_heap";
-import type { OrderRule } from "./_purryOrderRules";
-import { purryOrderRulesWithArgument } from "./_purryOrderRules";
+import {
+  purryOrderRulesWithArgument,
+  type OrderRule,
+} from "./_purryOrderRules";
 import type { CompareFunction, NonEmptyArray } from "./_types";
 
 /**
@@ -59,7 +61,7 @@ function dropFirstByImplementation<T>(
   }
 
   if (n <= 0) {
-    return data.slice();
+    return [...data];
   }
 
   const heap = data.slice(0, n);
diff --git a/src/dropLast.ts b/src/dropLast.ts
index 71193ef..137d839 100644
--- a/src/dropLast.ts
+++ b/src/dropLast.ts
@@ -32,7 +32,7 @@ export function dropLast(...args: ReadonlyArray<unknown>): unknown {
 }
 
 function _dropLast<T>(array: ReadonlyArray<T>, n: number): Array<T> {
-  const copy = array.slice();
+  const copy = [...array];
   if (n > 0) {
     copy.splice(-n);
   }
diff --git a/src/dropWhile.ts b/src/dropWhile.ts
index 8654af5..f81056c 100644
--- a/src/dropWhile.ts
+++ b/src/dropWhile.ts
@@ -44,9 +44,9 @@ function dropWhileImplementation<T>(
   data: ReadonlyArray<T>,
   predicate: (item: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): Array<T> {
-  for (let i = 0; i < data.length; i++) {
-    if (!predicate(data[i]!, i, data)) {
-      return data.slice(i);
+  for (const [index, item] of data.entries()) {
+    if (!predicate(item, index, data)) {
+      return data.slice(index);
     }
   }
   return [];
diff --git a/src/findLast.ts b/src/findLast.ts
index ab07ef1..bb5ccf4 100644
--- a/src/findLast.ts
+++ b/src/findLast.ts
@@ -71,7 +71,7 @@ export function findLast<T = never>(
 ): (data: ReadonlyArray<T>) => T | undefined;
 
 export function findLast(...args: ReadonlyArray<unknown>): unknown {
-  // TODO: Use Array.prototype.findLast once we bump our typescript target
+  // TODO: Use Array.prototype.findLast once we bump our target to ES2023+
   return purry(findLastImplementation, args);
 }
 
diff --git a/src/findLastIndex.ts b/src/findLastIndex.ts
index ca2be72..1ea0e6b 100644
--- a/src/findLastIndex.ts
+++ b/src/findLastIndex.ts
@@ -54,7 +54,7 @@ export function findLastIndex<T>(
 ): (array: ReadonlyArray<T>) => number;
 
 export function findLastIndex(...args: ReadonlyArray<unknown>): unknown {
-  // TODO: Use `Array.prototype.findLastIndex` once we bump our Typescript target.
+  // TODO: Use Array.prototype.findLastIndex once we bump our target to ES2023+
   return purry(findLastIndexImplementation, args);
 }
 
diff --git a/src/firstBy.ts b/src/firstBy.ts
index 2b13c93..98960d9 100644
--- a/src/firstBy.ts
+++ b/src/firstBy.ts
@@ -1,5 +1,4 @@
-import type { OrderRule } from "./_purryOrderRules";
-import { purryOrderRules } from "./_purryOrderRules";
+import { purryOrderRules, type OrderRule } from "./_purryOrderRules";
 import type {
   CompareFunction,
   IterableContainer,
diff --git a/src/flatMap.ts b/src/flatMap.ts
index a5e4600..33f33d9 100644
--- a/src/flatMap.ts
+++ b/src/flatMap.ts
@@ -1,4 +1,3 @@
-import { flatten } from "./flatten";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
@@ -23,14 +22,14 @@ import { purry } from "./purry";
  * @pipeable
  * @category Array
  */
-export function flatMap<T, K>(
+export function flatMap<T, U>(
   data: ReadonlyArray<T>,
   callbackfn: (
     input: T,
     index: number,
     data: ReadonlyArray<T>,
-  ) => K | ReadonlyArray<K>,
-): Array<K>;
+  ) => ReadonlyArray<U> | U,
+): Array<U>;
 
 /**
  * Returns a new array formed by applying a given callback function to each
@@ -52,29 +51,26 @@ export function flatMap<T, K>(
  * @pipeable
  * @category Array
  */
-export function flatMap<T, K>(
+export function flatMap<T, U>(
   callbackfn: (
     input: T,
     index: number,
     data: ReadonlyArray<T>,
-  ) => K | ReadonlyArray<K>,
-): (data: ReadonlyArray<T>) => Array<K>;
+  ) => ReadonlyArray<U> | U,
+): (data: ReadonlyArray<T>) => Array<U>;
 
 export function flatMap(...args: ReadonlyArray<unknown>): unknown {
   return purry(flatMapImplementation, args, lazyImplementation);
 }
 
-function flatMapImplementation<T, K>(
+const flatMapImplementation = <T, U>(
   data: ReadonlyArray<T>,
   callbackfn: (
-    input: T,
+    value: T,
     index: number,
     data: ReadonlyArray<T>,
-  ) => ReadonlyArray<K>,
-): Array<K> {
-  // TODO: Use flatMap directly once we bump our TypeScript target version.
-  return flatten(data.map(callbackfn));
-}
+  ) => ReadonlyArray<U> | U,
+): Array<U> => data.flatMap(callbackfn);
 
 const lazyImplementation =
   <T, K>(
diff --git a/src/forEach.ts b/src/forEach.ts
index 784d93a..0e2971e 100644
--- a/src/forEach.ts
+++ b/src/forEach.ts
@@ -62,7 +62,7 @@ function forEachImplementation<T>(
   data: ReadonlyArray<T>,
   callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => void,
 ): Array<T> {
-  // eslint-disable-next-line unicorn/no-array-for-each -- TODO: Once we bump our Typescript target version we can swap this with a proper for loop.
+  // eslint-disable-next-line unicorn/no-array-for-each -- We are intentionally proxying the built in forEach, it's up to the user to decide if they want to use a for loop instead.
   data.forEach(callbackfn);
   // @ts-expect-error [ts4104] - Because the dataFirst signature returns void this is only a problem when the dataLast function is used **outside** of a pipe; for these cases we warn the user that this is happening.
   return data;
diff --git a/src/fromEntries.ts b/src/fromEntries.ts
index efb0cd0..951f745 100644
--- a/src/fromEntries.ts
+++ b/src/fromEntries.ts
@@ -138,19 +138,5 @@ export function fromEntries(): <Entries extends IterableContainer<Entry>>(
 ) => Simplify<FromEntries<Entries>>;
 
 export function fromEntries(...args: ReadonlyArray<unknown>): unknown {
-  // TODO: When we bump the typescript target beyond ES2019 we can use Object.fromEntries directly here instead of our user-space implementation.
-  return purry(fromEntriesImplementation, args);
-}
-
-function fromEntriesImplementation<Entries extends IterableContainer<Entry>>(
-  entries: Entries,
-): FromEntries<Entries> {
-  const out: Partial<FromEntries<Entries>> = {};
-
-  for (const [key, value] of entries) {
-    // @ts-expect-error [ts7053] - Our input is generic so typescript is having a hard time inferring this.
-    out[key] = value;
-  }
-
-  return out as FromEntries<Entries>;
+  return purry(Object.fromEntries, args);
 }
diff --git a/src/fromKeys.ts b/src/fromKeys.ts
index 7289c7a..0841fab 100644
--- a/src/fromKeys.ts
+++ b/src/fromKeys.ts
@@ -83,11 +83,9 @@ function fromKeysImplementation<T extends IterableContainer<PropertyKey>, V>(
 ): FromKeys<T, V> {
   const result: Partial<FromKeys<T, V>> = {};
 
-  for (let i = 0; i < data.length; i++) {
-    // TODO: Use entries once we bump our typescript target.
-    const key = data[i]!;
+  for (const [index, key] of data.entries()) {
     // @ts-expect-error [ts7053] - There's no easy way to make Typescript aware that the items in T would be keys in the output object because it's type is built recursively and the "being an item of an array" property of a type is not "carried over" in the recursive type definition.
-    result[key] = mapper(key, i, data);
+    result[key] = mapper(key, index, data);
   }
 
   return result as FromKeys<T, V>;
diff --git a/src/groupBy.ts b/src/groupBy.ts
index 1d8e8dd..6198062 100644
--- a/src/groupBy.ts
+++ b/src/groupBy.ts
@@ -81,9 +81,7 @@ const groupByImplementation = <T, Key extends PropertyKey = PropertyKey>(
 ): ExactRecord<Key, NonEmptyArray<T>> => {
   const output: Partial<Record<Key, Array<T>>> = {};
 
-  for (let index = 0; index < data.length; index++) {
-    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-    const item = data[index]!;
+  for (const [index, item] of data.entries()) {
     const key = callbackfn(item, index, data);
     if (key !== undefined) {
       let { [key]: items } = output;
diff --git a/src/index.ts b/src/index.ts
index 9087dd8..d6e421d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -43,6 +43,7 @@ export * from "./indexBy";
 export * from "./intersectionWith";
 export * from "./invert";
 export * from "./isArray";
+export * from "./isBigInt";
 export * from "./isBoolean";
 export * from "./isDate";
 export * from "./isDeepEqual";
diff --git a/src/indexBy.ts b/src/indexBy.ts
index fc410d6..7bc166d 100644
--- a/src/indexBy.ts
+++ b/src/indexBy.ts
@@ -64,10 +64,8 @@ function indexByImplementation<T, K extends PropertyKey>(
 ): ExactRecord<K, T> {
   const out: Partial<Record<K, T>> = {};
 
-  for (let i = 0; i < data.length; i++) {
-    // TODO: Use entries directly once we bump our typescript target version.
-    const item = data[i]!;
-    const key = mapper(item, i, data);
+  for (const [index, item] of data.entries()) {
+    const key = mapper(item, index, data);
     out[key] = item;
   }
 
diff --git a/src/isBigInt.ts b/src/isBigInt.ts
new file mode 100644
index 0000000..2aeb77c
--- /dev/null
+++ b/src/isBigInt.ts
@@ -0,0 +1,19 @@
+import type { NarrowedTo } from "./_types";
+
+/**
+ * A function that checks if the passed parameter is a bigint and narrows its
+ * type accordingly.
+ *
+ * @param data - The variable to check.
+ * @returns True if the passed input is a number, false otherwise.
+ * @signature
+ *    R.isBigInt(data)
+ * @example
+ *    R.isBigInt(1n); // => true
+ *    R.isBigInt(1); // => false
+ *    R.isBigInt('notANumber'); // => false
+ * @category Guard
+ */
+export function isBigInt<T>(data: T | bigint): data is NarrowedTo<T, bigint> {
+  return typeof data === "bigint";
+}
diff --git a/src/isDeepEqual.ts b/src/isDeepEqual.ts
index fa975d0..7622de8 100644
--- a/src/isDeepEqual.ts
+++ b/src/isDeepEqual.ts
@@ -64,10 +64,10 @@ function isDeepEqualImplementation<T, S>(data: S | T, other: S): data is S {
     return true;
   }
 
-  if (typeof data === "number" && typeof other === "number") {
-    // TODO: This is a temporary fix for NaN, we should use Number.isNaN once we bump our target above ES5.
-    // eslint-disable-next-line no-self-compare -- We should use Number.isNaN here, but it's ES2015.
-    return data !== data && other !== other;
+  if (Object.is(data, other)) {
+    // We want to ignore the slight differences between `===` and `Object.is` as
+    // both of them largely define equality from a semantic point-of-view.
+    return true;
   }
 
   if (typeof data !== "object" || typeof other !== "object") {
@@ -94,11 +94,11 @@ function isDeepEqualImplementation<T, S>(data: S | T, other: S): data is S {
       return false;
     }
 
-    for (let i = 0; i < data.length; i++) {
+    for (const [index, item] of data.entries()) {
       if (
         !isDeepEqualImplementation(
-          data[i],
-          (other as ReadonlyArray<unknown>)[i],
+          item,
+          (other as ReadonlyArray<unknown>)[index],
         )
       ) {
         return false;
diff --git a/src/isIncludedIn.ts b/src/isIncludedIn.ts
index 7c20531..7b26209 100644
--- a/src/isIncludedIn.ts
+++ b/src/isIncludedIn.ts
@@ -73,5 +73,5 @@ export function isIncludedIn(
   }
 
   // === dataFirst ===
-  return container.indexOf(dataOrContainer) >= 0;
+  return container.includes(dataOrContainer);
 }
diff --git a/src/isNumber.ts b/src/isNumber.ts
index 7ddef7f..e6f8e9c 100644
--- a/src/isNumber.ts
+++ b/src/isNumber.ts
@@ -1,17 +1,19 @@
 import type { NarrowedTo } from "./_types";
 
 /**
- * A function that checks if the passed parameter is a number and narrows its type accordingly.
+ * A function that checks if the passed parameter is a number and narrows its
+ * type accordingly.
  *
  * @param data - The variable to check.
  * @returns True if the passed input is a number, false otherwise.
  * @signature
  *    R.isNumber(data)
  * @example
- *    R.isNumber(1) //=> true
- *    R.isNumber('notANumber') //=> false
+ *    R.isNumber(1); // => true
+ *    R.isNumber(1n); // => false
+ *    R.isNumber('notANumber'); // => false
  * @category Guard
  */
 export function isNumber<T>(data: T | number): data is NarrowedTo<T, number> {
-  return typeof data === "number" && !isNaN(data);
+  return typeof data === "number" && !Number.isNaN(data);
 }
diff --git a/src/last.ts b/src/last.ts
index 8835884..fcec526 100644
--- a/src/last.ts
+++ b/src/last.ts
@@ -40,5 +40,5 @@ export function last(...args: ReadonlyArray<unknown>): unknown {
 }
 
 function _last<T>(array: ReadonlyArray<T>): T | undefined {
-  return array[array.length - 1];
+  return array.at(-1);
 }
diff --git a/src/length.ts b/src/length.ts
index e37240c..d8f5a09 100644
--- a/src/length.ts
+++ b/src/length.ts
@@ -32,5 +32,5 @@ export function length(...args: ReadonlyArray<unknown>): unknown {
 }
 
 function _length<T>(items: Enumerable<T>): number {
-  return "length" in items ? items.length : Array.from(items).length;
+  return "length" in items ? items.length : [...items].length;
 }
diff --git a/src/mapToObj.ts b/src/mapToObj.ts
index fa4d48c..0ec6c18 100644
--- a/src/mapToObj.ts
+++ b/src/mapToObj.ts
@@ -67,10 +67,7 @@ function mapToObjImplementation(
 ): Record<PropertyKey, unknown> {
   const out: Record<PropertyKey, unknown> = {};
 
-  for (let index = 0; index < array.length; index++) {
-    // TODO: Use Array.prototype.entries once we bump the Typescript target version to iterate over the elements and index at the same time.
-    // eslint-disable-next-line @typescript-eslint/prefer-destructuring
-    const element = array[index];
+  for (const [index, element] of array.entries()) {
     const [key, value] = fn(element, index, array);
     out[key] = value;
   }
diff --git a/src/meanBy.ts b/src/meanBy.ts
index 4a71eb8..569b445 100644
--- a/src/meanBy.ts
+++ b/src/meanBy.ts
@@ -49,14 +49,12 @@ const meanByImplementation = <T>(
   fn: (value: T, index: number, data: ReadonlyArray<T>) => number,
 ): number => {
   if (array.length === 0) {
-    return NaN;
+    return Number.NaN;
   }
 
   let sum = 0;
 
-  for (let index = 0; index < array.length; index++) {
-    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-    const item = array[index]!;
+  for (const [index, item] of array.entries()) {
     sum += fn(item, index, array);
   }
 
diff --git a/src/mergeDeep.ts b/src/mergeDeep.ts
index 8b10be0..2a6167b 100644
--- a/src/mergeDeep.ts
+++ b/src/mergeDeep.ts
@@ -1,3 +1,4 @@
+import { isPlainObject } from "./isPlainObject";
 import { purry } from "./purry";
 import type { MergeDeep } from "./type-fest/merge-deep";
 
@@ -62,7 +63,7 @@ function mergeDeepImplementation<
     }
 
     const { [key]: destinationValue } = destination;
-    if (!isRecord(destinationValue)) {
+    if (!isPlainObject(destinationValue)) {
       // The value in destination is not a mergable object so the value from
       // source (which was already copied in the shallow merge) would be used
       // as-is.
@@ -70,7 +71,7 @@ function mergeDeepImplementation<
     }
 
     const { [key]: sourceValue } = source;
-    if (!isRecord(sourceValue)) {
+    if (!isPlainObject(sourceValue)) {
       // The value in source is not a mergable object either, so it will
       // override the object in destination.
       continue;
@@ -84,12 +85,3 @@ function mergeDeepImplementation<
   // @ts-expect-error [ts2322] - We build the output object iteratively, I don't think it's possible to improve the types here so that typescript infers the right type.
   return output;
 }
-
-// TODO: Replace this with a call to `isPlainObject` once PR #436 ships.
-function isRecord(object: unknown): object is Record<string, unknown> {
-  return (
-    typeof object === "object" &&
-    object !== null &&
-    Object.getPrototypeOf(object) === Object.prototype
-  );
-}
diff --git a/src/nthBy.ts b/src/nthBy.ts
index 2bc4f88..60b682f 100644
--- a/src/nthBy.ts
+++ b/src/nthBy.ts
@@ -1,5 +1,7 @@
-import type { OrderRule } from "./_purryOrderRules";
-import { purryOrderRulesWithArgument } from "./_purryOrderRules";
+import {
+  purryOrderRulesWithArgument,
+  type OrderRule,
+} from "./_purryOrderRules";
 import { quickSelect } from "./_quickSelect";
 import type {
   CompareFunction,
diff --git a/src/partition.ts b/src/partition.ts
index ba08b75..3d87eb2 100644
--- a/src/partition.ts
+++ b/src/partition.ts
@@ -68,9 +68,7 @@ const partitionImplementation = <T, S extends T>(
   predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
 ): [Array<T>, Array<T>] => {
   const ret: [Array<T>, Array<T>] = [[], []];
-  for (let index = 0; index < data.length; index++) {
-    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-    const item = data[index]!;
+  for (const [index, item] of data.entries()) {
     const matches = predicate(item, index, data);
     ret[matches ? 0 : 1].push(item);
   }
diff --git a/src/pipe.ts b/src/pipe.ts
index 5435ff9..d47d56f 100644
--- a/src/pipe.ts
+++ b/src/pipe.ts
@@ -279,26 +279,14 @@ export function pipe(
 
     const accumulator: Array<unknown> = [];
 
-    const iterator = output[Symbol.iterator]();
-
-    // eslint-disable-next-line no-constant-condition, @typescript-eslint/no-unnecessary-condition -- TODO: Once we bump the TS target version above ES5 we can use the built-in for-of loop instead.
-    while (true) {
-      const result = iterator.next();
-      if (result.done ?? false) {
-        break;
-      }
-
-      const shouldExitEarly = _processItem(
-        result.value,
-        accumulator,
-        lazySequence,
-      );
+    for (const value of output) {
+      const shouldExitEarly = _processItem(value, accumulator, lazySequence);
       if (shouldExitEarly) {
         break;
       }
     }
 
-    const { isSingle } = lazySequence[lazySequence.length - 1]!;
+    const { isSingle } = lazySequence.at(-1)!;
     output = isSingle ? accumulator[0] : accumulator;
     operationIndex += lazySequence.length;
   }
@@ -321,13 +309,7 @@ function _processItem(
 
   let lazyResult: LazyResult<any> = { done: false, hasNext: false };
   let isDone = false;
-  for (
-    let operationsIndex = 0;
-    operationsIndex < lazySequence.length;
-    operationsIndex++
-  ) {
-    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-    const lazyFn = lazySequence[operationsIndex]!;
+  for (const [operationsIndex, lazyFn] of lazySequence.entries()) {
     const { index, items } = lazyFn;
     items.push(currentItem);
     lazyResult = lazyFn(currentItem, index, items);
diff --git a/src/product.ts b/src/product.ts
index 766d33a..2555d49 100644
--- a/src/product.ts
+++ b/src/product.ts
@@ -1,7 +1,5 @@
 import { purry } from "./purry";
 
-// TODO: Support bigint once we bump our typescript version beyond ES5
-
 /**
  * Compute the product of the numbers in the array, or return 1 for an empty
  * array.
@@ -15,7 +13,9 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Number
  */
-export function product(data: ReadonlyArray<number>): number;
+export function product<
+  T extends ReadonlyArray<bigint> | ReadonlyArray<number>,
+>(data: T): T[number];
 
 /**
  * Compute the product of the numbers in the array, or return 1 for an empty
@@ -29,15 +29,22 @@ export function product(data: ReadonlyArray<number>): number;
  * @dataLast
  * @category Number
  */
-export function product(): (data: ReadonlyArray<number>) => number;
+export function product(): <
+  T extends ReadonlyArray<bigint> | ReadonlyArray<number>,
+>(
+  data: T,
+) => T[number];
 
 export function product(...args: ReadonlyArray<unknown>): unknown {
   return purry(productImplementation, args);
 }
 
-function productImplementation(data: ReadonlyArray<number>): number {
-  let out = 1;
+function productImplementation<
+  T extends ReadonlyArray<bigint> | ReadonlyArray<number>,
+>(data: T): T[number] {
+  let out = typeof data[0] === "bigint" ? 1n : 1;
   for (const value of data) {
+    // @ts-expect-error [ts2365] -- Typescript can't infer that all elements will be a number of the same type.
     out *= value;
   }
   return out;
diff --git a/src/pullObject.ts b/src/pullObject.ts
index 5b11c8d..2398569 100644
--- a/src/pullObject.ts
+++ b/src/pullObject.ts
@@ -95,11 +95,9 @@ function pullObjectImplementation<
 ): Partial<Record<K, V>> {
   const result: Partial<Record<K, V>> = {};
 
-  for (let i = 0; i < data.length; i++) {
-    // TODO: use entries once we bump our typescript target.
-    const item = data[i]!;
-    const key = keyExtractor(item, i, data);
-    const value = valueExtractor(item, i, data);
+  for (const [index, item] of data.entries()) {
+    const key = keyExtractor(item, index, data);
+    const value = valueExtractor(item, index, data);
     result[key] = value;
   }
 
diff --git a/src/rankBy.ts b/src/rankBy.ts
index 35774b5..6dbe4ff 100644
--- a/src/rankBy.ts
+++ b/src/rankBy.ts
@@ -1,5 +1,7 @@
-import type { OrderRule } from "./_purryOrderRules";
-import { purryOrderRulesWithArgument } from "./_purryOrderRules";
+import {
+  purryOrderRulesWithArgument,
+  type OrderRule,
+} from "./_purryOrderRules";
 import type { CompareFunction, NonEmptyArray } from "./_types";
 
 /**
diff --git a/src/reverse.ts b/src/reverse.ts
index d1264ba..04f8e48 100644
--- a/src/reverse.ts
+++ b/src/reverse.ts
@@ -41,9 +41,10 @@ export function reverse<T extends ReadonlyArray<unknown>>(): (
 ) => Reverse<T>;
 
 export function reverse(...args: ReadonlyArray<unknown>): unknown {
-  return purry(_reverse, args);
+  return purry(reverseImplementation, args);
 }
 
-function _reverse<T>(array: ReadonlyArray<T>): Array<T> {
-  return array.slice().reverse();
+function reverseImplementation<T>(array: ReadonlyArray<T>): Array<T> {
+  // TODO: Use `Array.prototype.toReversed` once we bump our target/lib beyond ES2022.
+  return [...array].reverse();
 }
diff --git a/src/sample.ts b/src/sample.ts
index 3521dea..cef8ce6 100644
--- a/src/sample.ts
+++ b/src/sample.ts
@@ -116,7 +116,7 @@ function sampleImplementation<T>(
 
   if (sampleSize >= data.length) {
     // Trivial
-    return data.slice();
+    return [...data];
   }
 
   if (sampleSize === 0) {
@@ -152,7 +152,7 @@ function sampleImplementation<T>(
   }
 
   if (sampleSize === actualSampleSize) {
-    return Array.from(sampleIndices)
+    return [...sampleIndices]
       .sort((a, b) => a - b)
       .map((index) => data[index]!);
   }
diff --git a/src/shuffle.ts b/src/shuffle.ts
index c0bc9b3..59a6ab0 100644
--- a/src/shuffle.ts
+++ b/src/shuffle.ts
@@ -30,7 +30,7 @@ export function shuffle(...args: ReadonlyArray<unknown>): unknown {
 }
 
 function _shuffle<T>(items: ReadonlyArray<T>): Array<T> {
-  const result = items.slice();
+  const result = [...items];
   for (let index = 0; index < items.length; index++) {
     const rand = index + Math.floor(Math.random() * (items.length - index));
     const value = result[rand]!;
diff --git a/src/sort.ts b/src/sort.ts
index 7a68699..af6aa2f 100644
--- a/src/sort.ts
+++ b/src/sort.ts
@@ -47,7 +47,7 @@ function sortImplementation<T extends IterableContainer>(
   items: T,
   cmp: (a: T[number], b: T[number]) => number,
 ): ReorderedArray<T> {
-  const ret = items.slice();
+  const ret = [...items];
   ret.sort(cmp);
   return ret as ReorderedArray<T>;
 }
diff --git a/src/sortBy.ts b/src/sortBy.ts
index 7b7aa13..2e2fdde 100644
--- a/src/sortBy.ts
+++ b/src/sortBy.ts
@@ -1,5 +1,4 @@
-import type { OrderRule } from "./_purryOrderRules";
-import { purryOrderRules } from "./_purryOrderRules";
+import { purryOrderRules, type OrderRule } from "./_purryOrderRules";
 import type {
   CompareFunction,
   IterableContainer,
@@ -100,5 +99,5 @@ const sortByImplementation = <T>(
   data: ReadonlyArray<T>,
   compareFn: CompareFunction<T>,
 ): Array<T> =>
-  // Sort is done in-place so we need to copy the array.
-  data.slice().sort(compareFn);
+  // TODO: Use `Array.prototype.toSorted` once we bump our target/lib beyond ES2022.
+  [...data].sort(compareFn);
diff --git a/src/splice.ts b/src/splice.ts
index ffb9ebd..a3d0e68 100644
--- a/src/splice.ts
+++ b/src/splice.ts
@@ -52,7 +52,8 @@ function _splice<T>(
   deleteCount: number,
   replacement: ReadonlyArray<T>,
 ): Array<T> {
-  const result = items.slice();
+  // TODO: Use `Array.prototype.toSpliced` once we bump our target/lib beyond ES2022.
+  const result = [...items];
   result.splice(start, deleteCount, ...replacement);
   return result;
 }
diff --git a/src/splitAt.ts b/src/splitAt.ts
index a5b3292..821d9f9 100644
--- a/src/splitAt.ts
+++ b/src/splitAt.ts
@@ -42,7 +42,7 @@ function _splitAt<T>(
   array: ReadonlyArray<T>,
   index: number,
 ): [Array<T>, Array<T>] {
-  const copy = array.slice();
+  const copy = [...array];
   const tail = copy.splice(index);
   return [copy, tail];
 }
diff --git a/src/splitWhen.ts b/src/splitWhen.ts
index e878db5..3fb0522 100644
--- a/src/splitWhen.ts
+++ b/src/splitWhen.ts
@@ -41,14 +41,11 @@ function splitWhenImplementation<T>(
   data: ReadonlyArray<T>,
   predicate: (item: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): [Array<T>, Array<T>] {
-  for (let i = 0; i < data.length; i++) {
-    // TODO: Use `Array.prototype.entries` once we bump our TS target so we
-    // can get both the index and the item at the same time, and don't need
-    // the non-null assertion.
-    if (predicate(data[i]!, i, data)) {
-      return splitAt(data, i);
+  for (const [index, item] of data.entries()) {
+    if (predicate(item, index, data)) {
+      return splitAt(data, index);
     }
   }
 
-  return [data.slice(), []];
+  return [[...data], []];
 }
diff --git a/src/sum.ts b/src/sum.ts
index 23026d8..c599d88 100644
--- a/src/sum.ts
+++ b/src/sum.ts
@@ -1,7 +1,5 @@
 import { purry } from "./purry";
 
-// TODO: Support bigint once we bump our typescript version beyond ES5
-
 /**
  * Sums the numbers in the array, or return 0 for an empty array.
  *
@@ -14,7 +12,9 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Number
  */
-export function sum(data: ReadonlyArray<number>): number;
+export function sum<T extends ReadonlyArray<bigint> | ReadonlyArray<number>>(
+  data: T,
+): T[number];
 
 /**
  * Sums the numbers in the array, or return 0 for an empty array.
@@ -27,15 +27,23 @@ export function sum(data: ReadonlyArray<number>): number;
  * @dataLast
  * @category Number
  */
-export function sum(): (data: ReadonlyArray<number>) => number;
+export function sum(): <
+  T extends ReadonlyArray<bigint> | ReadonlyArray<number>,
+>(
+  data: T,
+) => T[number];
 
 export function sum(...args: ReadonlyArray<unknown>): unknown {
   return purry(sumImplementation, args);
 }
 
-function sumImplementation(data: ReadonlyArray<number>): number {
-  let out = 0;
+function sumImplementation<
+  T extends ReadonlyArray<bigint> | ReadonlyArray<number>,
+>(data: T): T[number] {
+  let out = typeof data[0] === "bigint" ? 0n : 0;
   for (const value of data) {
+    // @ts-expect-error [ts2365] -- Typescript can't infer that all elements will be a number of the same type.
+    // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
     out += value;
   }
   return out;
diff --git a/src/sumBy.ts b/src/sumBy.ts
index 76ae728..7a04106 100644
--- a/src/sumBy.ts
+++ b/src/sumBy.ts
@@ -49,9 +49,7 @@ const sumByImplementation = <T>(
   callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => number,
 ): number => {
   let sum = 0;
-  for (let index = 0; index < array.length; index++) {
-    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-    const item = array[index]!;
+  for (const [index, item] of array.entries()) {
     const summand = callbackfn(item, index, array);
     sum += summand;
   }
diff --git a/src/swapIndices.ts b/src/swapIndices.ts
index 7f4965b..99c4c5b 100644
--- a/src/swapIndices.ts
+++ b/src/swapIndices.ts
@@ -171,9 +171,9 @@ function _swapArray(
   index1: number,
   index2: number,
 ): Array<unknown> {
-  const result = item.slice();
+  const result = [...item];
 
-  if (isNaN(index1) || isNaN(index2)) {
+  if (Number.isNaN(index1) || Number.isNaN(index2)) {
     return result;
   }
 
@@ -195,6 +195,6 @@ function _swapArray(
 }
 
 function _swapString(item: string, index1: number, index2: number): string {
-  const result = _swapArray(item.split(""), index1, index2);
+  const result = _swapArray([...item], index1, index2);
   return result.join("");
 }
diff --git a/src/takeFirstBy.ts b/src/takeFirstBy.ts
index 24585ee..e0f4200 100644
--- a/src/takeFirstBy.ts
+++ b/src/takeFirstBy.ts
@@ -1,6 +1,8 @@
 import { heapify, heapMaybeInsert } from "./_heap";
-import type { OrderRule } from "./_purryOrderRules";
-import { purryOrderRulesWithArgument } from "./_purryOrderRules";
+import {
+  purryOrderRulesWithArgument,
+  type OrderRule,
+} from "./_purryOrderRules";
 import type { CompareFunction, NonEmptyArray } from "./_types";
 
 /**
@@ -59,7 +61,7 @@ function takeFirstByImplementation<T>(
   }
 
   if (n >= data.length) {
-    return data.slice();
+    return [...data];
   }
 
   const heap = data.slice(0, n);
diff --git a/src/takeLastWhile.ts b/src/takeLastWhile.ts
index 2f5c783..6b1312b 100644
--- a/src/takeLastWhile.ts
+++ b/src/takeLastWhile.ts
@@ -47,5 +47,5 @@ function takeLastWhileImplementation<T>(
       return data.slice(i + 1);
     }
   }
-  return data.slice();
+  return [...data];
 }
diff --git a/src/takeWhile.ts b/src/takeWhile.ts
index 151ada7..dd4b8a7 100644
--- a/src/takeWhile.ts
+++ b/src/takeWhile.ts
@@ -41,10 +41,8 @@ function takeWhileImplementation<T>(
   predicate: (item: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): Array<T> {
   const ret: Array<T> = [];
-  for (let i = 0; i < data.length; i++) {
-    // TODO: Use entries once we bump our typescript target.
-    const item = data[i]!;
-    if (!predicate(item, i, data)) {
+  for (const [index, item] of data.entries()) {
+    if (!predicate(item, index, data)) {
       break;
     }
     ret.push(item);


Test patch:
diff --git a/src/differenceWith.test.ts b/src/differenceWith.test.ts
index ae86189..87ca137 100644
--- a/src/differenceWith.test.ts
+++ b/src/differenceWith.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { differenceWith } from "./differenceWith";
 import { isDeepEqual } from "./isDeepEqual";
 import { pipe } from "./pipe";
diff --git a/src/drop.test.ts b/src/drop.test.ts
index 9d2c073..e31703f 100644
--- a/src/drop.test.ts
+++ b/src/drop.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { drop } from "./drop";
 import { pipe } from "./pipe";
 import { take } from "./take";
diff --git a/src/first.test.ts b/src/first.test.ts
index 3d004fe..2ab4c91 100644
--- a/src/first.test.ts
+++ b/src/first.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { filter } from "./filter";
 import { first } from "./first";
 import { pipe } from "./pipe";
diff --git a/src/flatMap.test.ts b/src/flatMap.test.ts
index a9305b5..1e39235 100644
--- a/src/flatMap.test.ts
+++ b/src/flatMap.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { find } from "./find";
 import { flatMap } from "./flatMap";
 import { pipe } from "./pipe";
diff --git a/src/flatten.test.ts b/src/flatten.test.ts
index d02dd62..6bd3804 100644
--- a/src/flatten.test.ts
+++ b/src/flatten.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { find } from "./find";
 import { flatten } from "./flatten";
 import { pipe } from "./pipe";
diff --git a/src/flattenDeep.test.ts b/src/flattenDeep.test.ts
index 5c94f60..c707135 100644
--- a/src/flattenDeep.test.ts
+++ b/src/flattenDeep.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { find } from "./find";
 import { flattenDeep } from "./flattenDeep";
 import { pipe } from "./pipe";
diff --git a/src/intersectionWith.test.ts b/src/intersectionWith.test.ts
index 7b47930..87a2b9d 100644
--- a/src/intersectionWith.test.ts
+++ b/src/intersectionWith.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { intersectionWith } from "./intersectionWith";
 import { isDeepEqual } from "./isDeepEqual";
 import { pipe } from "./pipe";
diff --git a/src/isArray.test.ts b/src/isArray.test.ts
index 7903bce..1b83610 100644
--- a/src/isArray.test.ts
+++ b/src/isArray.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isArray } from "./isArray";
 
 describe("isArray", () => {
diff --git a/src/isBigInt.test.ts b/src/isBigInt.test.ts
new file mode 100644
index 0000000..07c9106
--- /dev/null
+++ b/src/isBigInt.test.ts
@@ -0,0 +1,40 @@
+import {
+  ALL_TYPES_DATA_PROVIDER,
+  TYPES_DATA_PROVIDER,
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
+import { isBigInt } from "./isBigInt";
+
+const dataFunction = (): string | 1n | 2n | 3n => 1n;
+
+describe("isBigInt", () => {
+  it("should work as type guard", () => {
+    const data = TYPES_DATA_PROVIDER.bigint as AllTypesDataProviderTypes;
+    if (isBigInt(data)) {
+      expect(typeof data).toEqual("bigint");
+      expectTypeOf(data).toEqualTypeOf<1n>();
+    }
+  });
+
+  it("should work as type guard in filter", () => {
+    const data = ALL_TYPES_DATA_PROVIDER.filter(isBigInt);
+    expect(data.every((c) => typeof c === "bigint")).toEqual(true);
+    expectTypeOf(data).toEqualTypeOf<Array<1n>>();
+  });
+
+  it("should work even if data type is unknown", () => {
+    const data = TYPES_DATA_PROVIDER.bigint as unknown;
+    if (isBigInt(data)) {
+      expect(typeof data).toEqual("bigint");
+      expectTypeOf(data).toEqualTypeOf<bigint>();
+    }
+  });
+
+  it("should work with literal types", () => {
+    const x = dataFunction();
+    if (isBigInt(x)) {
+      expect(typeof x).toEqual("bigint");
+      expectTypeOf(x).toEqualTypeOf<1n | 2n | 3n>(x);
+    }
+  });
+});
diff --git a/src/isBoolean.test.ts b/src/isBoolean.test.ts
index 6792fca..6d1a9a2 100644
--- a/src/isBoolean.test.ts
+++ b/src/isBoolean.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isBoolean } from "./isBoolean";
 
 describe("isBoolean", () => {
diff --git a/src/isDate.test.ts b/src/isDate.test.ts
index a932f8e..cc59afa 100644
--- a/src/isDate.test.ts
+++ b/src/isDate.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isDate } from "./isDate";
 
 describe("isDate", () => {
diff --git a/src/isDefined.test.ts b/src/isDefined.test.ts
index dae13bb..3952233 100644
--- a/src/isDefined.test.ts
+++ b/src/isDefined.test.ts
@@ -1,18 +1,16 @@
-import type {
-  AllTypesDataProviderTypes,
-  TestClass,
-} from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+  type TestClass,
+} from "../test/typesDataProvider";
 import { isDefined } from "./isDefined";
 
 describe("isDefined", () => {
   it("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.date as AllTypesDataProviderTypes;
     if (isDefined(data)) {
-      expect(data instanceof Date).toEqual(true);
+      expect(data instanceof Date).toBe(true);
       expectTypeOf(data).toEqualTypeOf<
         | Array<number>
         | Date
@@ -27,6 +25,7 @@ describe("isDefined", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
@@ -37,7 +36,7 @@ describe("isDefined", () => {
 
   it("should work as type guard in filter", () => {
     const data = ALL_TYPES_DATA_PROVIDER.filter(isDefined);
-    expect(data).toHaveLength(17);
+    expect(data).toHaveLength(18);
     expectTypeOf(data).toEqualTypeOf<
       Array<
         | Array<number>
@@ -53,6 +52,7 @@ describe("isDefined", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
diff --git a/src/isError.test.ts b/src/isError.test.ts
index e101156..296e271 100644
--- a/src/isError.test.ts
+++ b/src/isError.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isError } from "./isError";
 
 class MyError extends Error {
diff --git a/src/isFunction.test.ts b/src/isFunction.test.ts
index 98750bb..16ce7ca 100644
--- a/src/isFunction.test.ts
+++ b/src/isFunction.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isFunction } from "./isFunction";
 
 describe("isFunction", () => {
diff --git a/src/isNonNull.test.ts b/src/isNonNull.test.ts
index 44b991d..c2324c7 100644
--- a/src/isNonNull.test.ts
+++ b/src/isNonNull.test.ts
@@ -1,18 +1,16 @@
-import type {
-  AllTypesDataProviderTypes,
-  TestClass,
-} from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+  type TestClass,
+} from "../test/typesDataProvider";
 import { isNonNull } from "./isNonNull";
 
 describe("isNonNull", () => {
   test("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.date as AllTypesDataProviderTypes;
     if (isNonNull(data)) {
-      expect(data instanceof Date).toEqual(true);
+      expect(data instanceof Date).toBe(true);
       expectTypeOf(data).toEqualTypeOf<
         | Array<number>
         | Date
@@ -27,6 +25,7 @@ describe("isNonNull", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
@@ -36,7 +35,7 @@ describe("isNonNull", () => {
   });
   test("should work as type guard in filter", () => {
     const data = ALL_TYPES_DATA_PROVIDER.filter(isNonNull);
-    expect(data).toHaveLength(17);
+    expect(data).toHaveLength(18);
     expectTypeOf(data).toEqualTypeOf<
       Array<
         | Array<number>
@@ -52,6 +51,7 @@ describe("isNonNull", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
diff --git a/src/isNonNullish.test.ts b/src/isNonNullish.test.ts
index 60b45bc..a4dcd1f 100644
--- a/src/isNonNullish.test.ts
+++ b/src/isNonNullish.test.ts
@@ -1,18 +1,16 @@
-import type {
-  AllTypesDataProviderTypes,
-  TestClass,
-} from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+  type TestClass,
+} from "../test/typesDataProvider";
 import { isNonNullish } from "./isNonNullish";
 
 describe("isNonNullish", () => {
   it("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.date as AllTypesDataProviderTypes;
     if (isNonNullish(data)) {
-      expect(data instanceof Date).toEqual(true);
+      expect(data instanceof Date).toBe(true);
       expectTypeOf(data).toEqualTypeOf<
         | Array<number>
         | Date
@@ -27,6 +25,7 @@ describe("isNonNullish", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
@@ -35,7 +34,7 @@ describe("isNonNullish", () => {
   });
   it("should work as type guard in filter", () => {
     const data = ALL_TYPES_DATA_PROVIDER.filter(isNonNullish);
-    expect(data).toHaveLength(16);
+    expect(data).toHaveLength(17);
     expectTypeOf(data).toEqualTypeOf<
       Array<
         | Array<number>
@@ -51,6 +50,7 @@ describe("isNonNullish", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
diff --git a/src/isNot.test.ts b/src/isNot.test.ts
index 54c1b23..0bbc9a8 100644
--- a/src/isNot.test.ts
+++ b/src/isNot.test.ts
@@ -1,11 +1,9 @@
-import type {
-  AllTypesDataProviderTypes,
-  TestClass,
-} from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+  type TestClass,
+} from "../test/typesDataProvider";
 import { isNot } from "./isNot";
 import { isPromise } from "./isPromise";
 import { isString } from "./isString";
@@ -14,7 +12,7 @@ describe("isNot", () => {
   it("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.promise as AllTypesDataProviderTypes;
     if (isNot(isString)(data)) {
-      expect(data instanceof Promise).toEqual(true);
+      expect(data instanceof Promise).toBe(true);
       expectTypeOf(data).toEqualTypeOf<
         | Array<number>
         | Date
@@ -28,6 +26,7 @@ describe("isNot", () => {
         | boolean
         | number
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
@@ -39,7 +38,7 @@ describe("isNot", () => {
 
   it("should work as type guard in filter", () => {
     const data = ALL_TYPES_DATA_PROVIDER.filter(isNot(isPromise));
-    expect(data.some((c) => c instanceof Promise)).toEqual(false);
+    expect(data.some((c) => c instanceof Promise)).toBe(false);
     expectTypeOf(data).toEqualTypeOf<
       Array<
         | Array<number>
@@ -54,6 +53,7 @@ describe("isNot", () => {
         | number
         | string
         | symbol
+        | 1n
         | (() => void)
         | { readonly a: "asd" }
         | [number, number, number]
diff --git a/src/isNullish.test.ts b/src/isNullish.test.ts
index 86ecb2b..969f8a0 100644
--- a/src/isNullish.test.ts
+++ b/src/isNullish.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isNullish } from "./isNullish";
 
 describe("isNullish", () => {
diff --git a/src/isNumber.test.ts b/src/isNumber.test.ts
index 30adef1..20f4b0a 100644
--- a/src/isNumber.test.ts
+++ b/src/isNumber.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isNumber } from "./isNumber";
 
 const dataFunction = (): string | 1 | 2 | 3 => 1;
diff --git a/src/isObjectType.test.ts b/src/isObjectType.test.ts
index 4f5df73..d09285d 100644
--- a/src/isObjectType.test.ts
+++ b/src/isObjectType.test.ts
@@ -1,9 +1,9 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
   TestClass,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isObjectType } from "./isObjectType";
 
 describe("runtime", () => {
diff --git a/src/isPlainObject.test.ts b/src/isPlainObject.test.ts
index fea2889..5a6fc9f 100644
--- a/src/isPlainObject.test.ts
+++ b/src/isPlainObject.test.ts
@@ -1,9 +1,9 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
   TestClass,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isPlainObject } from "./isPlainObject";
 
 describe("runtime", () => {
diff --git a/src/isPromise.test.ts b/src/isPromise.test.ts
index dd6fa5d..8fe7ff8 100644
--- a/src/isPromise.test.ts
+++ b/src/isPromise.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isPromise } from "./isPromise";
 
 describe("isPromise", () => {
diff --git a/src/isString.test.ts b/src/isString.test.ts
index 333715e..02883a2 100644
--- a/src/isString.test.ts
+++ b/src/isString.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isString } from "./isString";
 
 const dataFunction = (): number | "a" | "b" | "c" => "a";
diff --git a/src/isSymbol.test.ts b/src/isSymbol.test.ts
index 6ee2aec..a7579e4 100644
--- a/src/isSymbol.test.ts
+++ b/src/isSymbol.test.ts
@@ -1,8 +1,8 @@
-import type { AllTypesDataProviderTypes } from "../test/types_data_provider";
 import {
   ALL_TYPES_DATA_PROVIDER,
   TYPES_DATA_PROVIDER,
-} from "../test/types_data_provider";
+  type AllTypesDataProviderTypes,
+} from "../test/typesDataProvider";
 import { isSymbol } from "./isSymbol";
 
 describe("isSymbol", () => {
diff --git a/src/last.test.ts b/src/last.test.ts
index 06a2779..ef0a06c 100644
--- a/src/last.test.ts
+++ b/src/last.test.ts
@@ -37,7 +37,7 @@ describe("last", () => {
     });
 
     test("should infer type in pipes", () => {
-      const data = pipe("this is a text", (text) => text.split(""), last());
+      const data = pipe("this is a text", (text) => [...text], last());
       assertType<string | undefined>(data);
     });
   });
diff --git a/src/mapWithFeedback.test.ts b/src/mapWithFeedback.test.ts
index ebcc9d5..b27dfdc 100644
--- a/src/mapWithFeedback.test.ts
+++ b/src/mapWithFeedback.test.ts
@@ -112,7 +112,7 @@ describe("typing", () => {
   it("should return a tuple consisting of the initial value type even if the initial iterable contains a different type", () => {
     const result = mapWithFeedback(
       ["1", "2", "3", "4", "5"],
-      (acc, x) => acc + parseInt(x),
+      (acc, x) => acc + Number.parseInt(x),
       100,
     );
     expectTypeOf(result).toEqualTypeOf<
diff --git a/src/product.test.ts b/src/product.test.ts
index 8c56768..4d1d093 100644
--- a/src/product.test.ts
+++ b/src/product.test.ts
@@ -13,6 +13,10 @@ describe("runtime", () => {
     it("should return 1 for an empty array", () => {
       expect(product([])).toBe(1);
     });
+
+    it("works with bigints", () => {
+      expect(product([1n, 2n, 3n])).toBe(6n);
+    });
   });
 
   describe("dataLast", () => {
@@ -28,3 +32,32 @@ describe("runtime", () => {
     });
   });
 });
+
+describe("typing", () => {
+  it("returns number on numbers", () => {
+    const result = product([1, 2, 3]);
+    expectTypeOf(result).toEqualTypeOf<number>();
+  });
+
+  it("returns bigint on bigints", () => {
+    const result = product([1n, 2n, 3n]);
+    expectTypeOf(result).toEqualTypeOf<bigint>();
+  });
+
+  it("doesn't allow mixed arrays", () => {
+    expect(() =>
+      // @ts-expect-error [ts2345] - Can't product bigints and numbers...
+      product([1, 2n]),
+    ).toThrowErrorMatchingInlineSnapshot(
+      `[TypeError: Cannot mix BigInt and other types, use explicit conversions]`,
+    );
+  });
+});
+
+describe("KNOWN ISSUES", () => {
+  it("Returns 1 (`number`) instead of 1n (`bigint`) for empty `bigint` arrays", () => {
+    const result = product([] as Array<bigint>);
+    expect(result).toBe(1);
+    expectTypeOf(result).toEqualTypeOf<bigint>();
+  });
+});
diff --git a/src/rankBy.test.ts b/src/rankBy.test.ts
index aee61bc..f9a4be6 100644
--- a/src/rankBy.test.ts
+++ b/src/rankBy.test.ts
@@ -9,11 +9,8 @@ describe("runtime (dataFirst)", () => {
   it("maintains the rank for items already in the array", () => {
     const data = [5, 1, 3] as const;
     const sorted = [...data].sort((a, b) => a - b);
-    for (let i = 0; i < sorted.length; i++) {
-      // TODO: Use `Array.prototype.entries` once we bump our TS target so we
-      // can get both the index and the item at the same time, and don't need
-      // the non-null assertion.
-      expect(rankBy(data, sorted[i]!, identity())).toBe(i);
+    for (const [index, item] of sorted.entries()) {
+      expect(rankBy(data, item, identity())).toBe(index);
     }
   });
 
diff --git a/src/sample.test.ts b/src/sample.test.ts
index 721adce..f9a15fc 100644
--- a/src/sample.test.ts
+++ b/src/sample.test.ts
@@ -920,7 +920,7 @@ describe("typing", () => {
 const generateRandomArray = (): NonEmptyArray<number> =>
   // We use a set to remove duplicates, as it allows us to simplify our tests
   // @ts-expect-error [ts2322]: we know this array isn't empty!
-  Array.from(new Set(Array.from({ length: 100 }).map(() => Math.random())));
+  [...new Set(Array.from({ length: 100 }).map(() => Math.random()))];
 
 const allIndices = (array: ReadonlyArray<unknown>): Array<number> =>
   array.map((_, index) => index);
diff --git a/src/sum.test.ts b/src/sum.test.ts
index e36ad8b..726e04a 100644
--- a/src/sum.test.ts
+++ b/src/sum.test.ts
@@ -12,6 +12,10 @@ describe("runtime", () => {
     it("should return 0 for an empty array", () => {
       expect(sum([])).toBe(0);
     });
+
+    it("works on bigints", () => {
+      expect(sum([1n, 2n, 3n])).toBe(6n);
+    });
   });
 
   describe("dataLast", () => {
@@ -26,3 +30,32 @@ describe("runtime", () => {
     });
   });
 });
+
+describe("typing", () => {
+  it("returns number on numbers", () => {
+    const result = sum([1, 2, 3]);
+    expectTypeOf(result).toEqualTypeOf<number>();
+  });
+
+  it("returns bigint on bigints", () => {
+    const result = sum([1n, 2n, 3n]);
+    expectTypeOf(result).toEqualTypeOf<bigint>();
+  });
+
+  it("doesn't allow mixed arrays", () => {
+    expect(() =>
+      // @ts-expect-error [ts2345] - Can't sum bigints and numbers...
+      sum([1, 2n]),
+    ).toThrowErrorMatchingInlineSnapshot(
+      `[TypeError: Cannot mix BigInt and other types, use explicit conversions]`,
+    );
+  });
+});
+
+describe("KNOWN ISSUES", () => {
+  it("Returns 0 (`number`) instead of 0n (`bigint`) for empty `bigint` arrays", () => {
+    const result = sum([] as Array<bigint>);
+    expect(result).toBe(0);
+    expectTypeOf(result).toEqualTypeOf<bigint>();
+  });
+});
diff --git a/src/unique.test.ts b/src/unique.test.ts
index 8174f2a..e8af1d7 100644
--- a/src/unique.test.ts
+++ b/src/unique.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { pipe } from "./pipe";
 import { take } from "./take";
 import { unique } from "./unique";
diff --git a/src/uniqueBy.test.ts b/src/uniqueBy.test.ts
index 75732ed..25f4d5c 100644
--- a/src/uniqueBy.test.ts
+++ b/src/uniqueBy.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { identity } from "./identity";
 import { pipe } from "./pipe";
 import { take } from "./take";
diff --git a/src/uniqueWith.test.ts b/src/uniqueWith.test.ts
index 5693d7f..ba08438 100644
--- a/src/uniqueWith.test.ts
+++ b/src/uniqueWith.test.ts
@@ -1,4 +1,4 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { createLazyInvocationCounter } from "../test/lazyInvocationCounter";
 import { isDeepEqual } from "./isDeepEqual";
 import { pipe } from "./pipe";
 import { take } from "./take";
diff --git a/test/lazy_invocation_counter.ts b/test/lazyInvocationCounter.ts
similarity index 65%
rename from test/lazy_invocation_counter.ts
rename to test/lazyInvocationCounter.ts
index b3bf51b..e82d74e 100644
--- a/test/lazy_invocation_counter.ts
+++ b/test/lazyInvocationCounter.ts
@@ -1,5 +1,6 @@
 import { map } from "../src/map";
 
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types
 export const createLazyInvocationCounter = () => {
   const count = vi.fn();
   return {
diff --git a/test/types_data_provider.ts b/test/typesDataProvider.ts
similarity index 92%
rename from test/types_data_provider.ts
rename to test/typesDataProvider.ts
index 524c112..83337c3 100644
--- a/test/types_data_provider.ts
+++ b/test/typesDataProvider.ts
@@ -7,6 +7,7 @@ export class TestClass {
 
 export const TYPES_DATA_PROVIDER = {
   array: [1, 2, 3] as Array<number>,
+  bigint: 1n,
   boolean: false as boolean,
   date: new Date("1985-07-24T07:40:00.000Z"),
   error: new Error("asd"),
@@ -15,6 +16,7 @@ export const TYPES_DATA_PROVIDER = {
   },
   instance: new TestClass(),
   map: new Map<string, string>(),
+  // eslint-disable-next-line unicorn/no-null -- Intentional
   null: null,
   number: 5 as number,
   object: { a: "asd" },