Repository: remeda/remeda
Pull request #880 (2024-10-06 18:56:01)

chore(RFC): enable word wrapping in vscode
Better-comments is part of our recommended extensions. It's great, but at least it's default config doesn't handle multi-line TODOs very well. It doesn't group all the lines after the TODO as part of the same comment so it colors the first line differently than the rest.

We can mitigate this globally for all maintainers (using vscode) by adding some settings to the vscode settings file and rewrite the todo omments to be single line.

This matches the already existing issue where `eslint-disable -- <reason>` comments are already single-line and can't be configured in eslint.

Before:

![image](https://github.com/user-attachments/assets/c56e0897-e511-4191-991c-dc598a32a1f6)

After:

![image](https://github.com/user-attachments/assets/9e6eb0da-1a27-46ef-a47d-2c83cd361497)

It actually makes PRs a little easier to read because these walls of text don't render well on my laptop screen which doesn't have 80 characters for the side-by-side mode.

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/880/builds/548481) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 935af98574717517f791adad222aa968c92dc130:

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

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/remeda/remeda/pull/880?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`0420a9e`)](https://app.codecov.io/gh/remeda/remeda/commit/0420a9e877ef9a11e549845bbaa7d0b6c6cf11c1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda) to head [(`935af98`)](https://app.codecov.io/gh/remeda/remeda/commit/935af98574717517f791adad222aa968c92dc130?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #880   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          315       315           
  Lines        10777     10777           
  Branches      2651      2651           
=========================================
  Hits         10777     10777           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/remeda/remeda/pull/880?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=remeda).


Patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9cee17a..1557e3c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,5 +13,11 @@
   // It's better to ensure that the project typescript is the one we use for the
   // project so that people don't build for newer typescript features before we
   // bump it in our project first.
-  "typescript.tsdk": "node_modules/typescript/lib"
+  "typescript.tsdk": "node_modules/typescript/lib",
+
+  // eslint disable comments and better-comments syntax highlighting only work
+  // on a single line, so this setting makes them "feel" like they observe our
+  // prettier `printWidth` setting.
+  "editor.wordWrap": "wordWrapColumn",
+  "editor.wordWrapColumn": 80
 }
diff --git a/docs/src/lib/transform.ts b/docs/src/lib/transform.ts
index 4aa70c2..c6a09e7 100644
--- a/docs/src/lib/transform.ts
+++ b/docs/src/lib/transform.ts
@@ -204,8 +204,7 @@ function createCategoriesLookup(
       continue;
     }
 
-    // TODO: We can enforce that only a predefined set of categories is
-    // acceptable and break the build on any unknown categories
+    // TODO: We can enforce that only a predefined set of categories is acceptable and break the build on any unknown categories
     for (const id of children) {
       result.set(id, title);
     }
diff --git a/eslint.config.js b/eslint.config.js
index 5b7f9ed..3b20e37 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -48,9 +48,7 @@ export default tseslint.config(
       // now instead...
       "@typescript-eslint/no-confusing-void-expression": "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.
+      // 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.
       "@typescript-eslint/naming-convention": "off",
       "unicorn/prevent-abbreviations": "off",
 
@@ -451,10 +449,7 @@ export default tseslint.config(
       // without really adding any interesting semantics.
       "vitest/require-top-level-describe": "off",
 
-      // TODO: When none of the "onlyXXX" options are enabled this rule isn't
-      // valuable and doesn't improve the quality of the tests. We can consider
-      // enabling some (or all of) those options and see if it makes the tests
-      // better without the code being a mess.
+      // TODO: When none of the "onlyXXX" options are enabled this rule isn't valuable and doesn't improve the quality of the tests. We can consider enabling some (or all of) those options and see if it makes the tests better without the code being a mess.
       "vitest/prefer-expect-assertions": "off",
 
       // These aren't valuable when writing tests, they'll just make the tests
@@ -478,10 +473,7 @@ export default tseslint.config(
       // some weird setup.
       "vitest/no-hooks": "off",
 
-      // TODO: This rule might be useful to guide people to break tests into
-      // smaller tests that only expect one thing, but there's no reasonable
-      // max value we can configure it to that won't end up feeling arbitrary
-      // and noisy.
+      // TODO: This rule might be useful to guide people to break tests into smaller tests that only expect one thing, but there's no reasonable max value we can configure it to that won't end up feeling arbitrary and noisy.
       "vitest/max-expects": "off",
     },
   },
diff --git a/src/drop.ts b/src/drop.ts
index a771c0d..bbb35cf 100644
--- a/src/drop.ts
+++ b/src/drop.ts
@@ -18,9 +18,7 @@ type Drop<T extends IterableContainer, N extends number> =
         // fallback to the "legacy" typing where we convert our output to a
         // simple array. This is also the case when N is not a literal value
         // (e.g. it is `number`).
-        // TODO: We can improve this type by returning a union of all possible
-        // dropped shapes (e.g. the equivalent of Drop<T, 1> | Drop<T, 2> |
-        // Drop<T, 3> | ...).
+        // TODO: We can improve this type by returning a union of all possible dropped shapes (e.g. the equivalent of Drop<T, 1> | Drop<T, 2> | Drop<T, 3> | ...).
         Array<T[number]>
       : // We have an non-negative integer N so we start chopping up the array.
         // first we take a look at its prefix:
diff --git a/src/internal/types.ts b/src/internal/types.ts
index d53bc56..369f368 100644
--- a/src/internal/types.ts
+++ b/src/internal/types.ts
@@ -205,9 +205,7 @@ export type UpsertProp<T, K extends PropertyKey, V> = Simplify<
 type IsSingleLiteral<K> =
   IsLiteral<K> extends true ? (IsUnion<K> extends true ? false : true) : false;
 
-// TODO: This type is copied from type-fest because it isn't
-// exported. It's part of the "internal" types. We should check back in a while
-// to see if this type is added to the public offering.
+// TODO: This type is copied from type-fest because it isn't exported. It's part of the "internal" types. We should check back in a while to see if this type is added to the public offering.
 export type IsUnion<T> = InternalIsUnion<T>;
 type InternalIsUnion<T, U = T> = (
   IsNever<T> extends true
diff --git a/src/swapIndices.ts b/src/swapIndices.ts
index c6d5520..f8f62dd 100644
--- a/src/swapIndices.ts
+++ b/src/swapIndices.ts
@@ -66,12 +66,7 @@ type SwapArray<
   K1 extends number,
   K2 extends number,
 > =
-  // TODO: Because of limitations on the typescript version used in Remeda we
-  // can't build a proper Absolute number type so we can't implement proper
-  // typing for negative indices and have to opt for a less- strict type
-  // instead.
-  // Check out the history for the PR that introduced this TODO to see how it
-  // could be implemented.
+  // TODO: Because of limitations on the typescript version used in Remeda we can't build a proper Absolute number type so we can't implement proper typing for negative indices and have to opt for a less- strict type instead. Check out the history for the PR that introduced this TODO to see how it could be implemented.
   IsNonNegative<K1> extends false
     ? Array<T[number]>
     : IsNonNegative<K2> extends false
diff --git a/tsup.config.ts b/tsup.config.ts
index af05b6b..d232ac8 100644
--- a/tsup.config.ts
+++ b/tsup.config.ts
@@ -31,9 +31,7 @@ export default defineConfig(async () => ({
  * our library for. This allows the library to be tree-shaken so that bundlers
  * can take just the utilities and functionality the user's project needs.
  *
- * TODO: We need this just because tsup doesn't support globs in the entry
- * field. If tsup starts supporting it we can drop this and replace it with a
- * regular glob.
+ * TODO: We need this just because tsup doesn't support globs in the entry field. If tsup starts supporting it we can drop this and replace it with a regular glob.
  */
 async function getEntries(sourceDirectory: string): Promise<Array<string>> {
   const files = await ts.readdir(sourceDirectory);


Test patch:
diff --git a/src/chunk.test-d.ts b/src/chunk.test-d.ts
index 8d5fd17..cdd7c0f 100644
--- a/src/chunk.test-d.ts
+++ b/src/chunk.test-d.ts
@@ -148,8 +148,7 @@ describe("literal size", () => {
                 ...Array<[boolean, boolean]>,
                 [boolean, boolean] | [boolean],
               ]
-            // TODO: Ideally the following two lines should be folded into a
-            // single line: `[[number] | [number, boolean]]`
+            // TODO: Ideally the following two lines should be folded into a single line: `[[number] | [number, boolean]]`
             | [[number, boolean]]
             | [[number]]
           >();
@@ -182,8 +181,7 @@ describe("literal size", () => {
                 ...Array<[boolean, boolean]>,
                 [boolean, boolean] | [boolean],
               ]
-            // TODO: Ideally the following two lines should be folded into a
-            // single line: `[[number, number], [number] | [number, boolean]]`
+            // TODO: Ideally the following two lines should be folded into a single line: `[[number, number], [number] | [number, boolean]]`
             | [[number, number], [number, boolean]]
             | [[number, number], [number]]
           >();
@@ -234,9 +232,7 @@ describe("literal size", () => {
         expectTypeOf(result).toEqualTypeOf<
           | [[number, boolean], ...Array<[boolean, boolean]>, [boolean, string]]
           | [[number, boolean], ...Array<[boolean, boolean]>, [string]]
-          // TODO: This is the same type as the previous line (for an empty
-          // array), but it's hard to build the types to be aware of it because
-          // they come from different branches of the type.
+          // TODO: This is the same type as the previous line (for an empty array), but it's hard to build the types to be aware of it because they come from different branches of the type.
           | [[number, boolean], [string]]
           | [[number, string]]
         >();
@@ -280,8 +276,7 @@ describe("literal size", () => {
                 ...Array<[boolean, boolean]>,
                 [boolean, boolean] | [boolean],
               ]
-            // TODO: Ideally the following two lines should be folded into a
-            // single line: `[[number] | [number, boolean]]`
+            // TODO: Ideally the following two lines should be folded into a single line: `[[number] | [number, boolean]]`
             | [[number, boolean]]
             | [[number]]
           >();
@@ -314,8 +309,7 @@ describe("literal size", () => {
                 ...Array<[boolean, boolean]>,
                 [boolean, boolean] | [boolean],
               ]
-            // TODO: Ideally the following two lines should be folded into a
-            // single line: `[[number, number], [number] | [number, boolean]]`
+            // TODO: Ideally the following two lines should be folded into a single line: `[[number, number], [number] | [number, boolean]]`
             | [[number, number], [number, boolean]]
             | [[number, number], [number]]
           >();
@@ -366,9 +360,7 @@ describe("literal size", () => {
         expectTypeOf(result).toEqualTypeOf<
           | [[number, boolean], ...Array<[boolean, boolean]>, [boolean, string]]
           | [[number, boolean], ...Array<[boolean, boolean]>, [string]]
-          // TODO: This is the same type as the previous line (for an empty
-          // array), but it's hard to build the types to be aware of it because
-          // they come from different branches of the type.
+          // TODO: This is the same type as the previous line (for an empty array), but it's hard to build the types to be aware of it because they come from different branches of the type.
           | [[number, boolean], [string]]
           | [[number, string]]
         >();
diff --git a/src/filter.test-d.ts b/src/filter.test-d.ts
index de07f0f..7d873d0 100644
--- a/src/filter.test-d.ts
+++ b/src/filter.test-d.ts
@@ -41,6 +41,5 @@ describe("data_last", () => {
   });
 });
 
-// TODO: The Remeda `isNumber` utility isn't narrowing our types correctly for
-// our tests here.
+// TODO: The Remeda `isNumber` utility isn't narrowing our types correctly for our tests here.
 const isNumber = <T>(x: T): x is Extract<T, number> => typeof x === "number";
diff --git a/src/filter.test.ts b/src/filter.test.ts
index e9cca7e..77a1ccb 100644
--- a/src/filter.test.ts
+++ b/src/filter.test.ts
@@ -60,6 +60,5 @@ describe("data_last", () => {
   });
 });
 
-// TODO: The Remeda `isNumber` utility isn't narrowing our types correctly for
-// our tests here.
+// TODO: The Remeda `isNumber` utility isn't narrowing our types correctly for our tests here.
 const isNumber = <T>(x: T): x is Extract<T, number> => typeof x === "number";
diff --git a/src/internal/heap.test.ts b/src/internal/heap.test.ts
index 77b9c90..9bd3c3c 100644
--- a/src/internal/heap.test.ts
+++ b/src/internal/heap.test.ts
@@ -1,9 +1,6 @@
 import { heapMaybeInsert } from "./heap";
 
-// TODO: This file only tests edge-cases which cannot be reached via the public
-// functions that use the heap functions. We can expand the testing for heap so
-// that we aren't dependant on those functions to test the core heap
-// functionality.
+// TODO: This file only tests edge-cases which cannot be reached via the public functions that use the heap functions. We can expand the testing for heap so that we aren't dependant on those functions to test the core heap functionality.
 
 describe("heapMaybeInsert", () => {
   test("it works trivially on an empty heap", () => {
diff --git a/src/isEmpty.test-d.ts b/src/isEmpty.test-d.ts
index 6b04751..c4952a5 100644
--- a/src/isEmpty.test-d.ts
+++ b/src/isEmpty.test-d.ts
@@ -1,23 +1,64 @@
 import { isEmpty } from "./isEmpty";
 
-test("does not accept invalid input types", () => {
-  // @ts-expect-error [ts2769] number is not a valid input type
-  isEmpty(2);
+describe("invalid types", () => {
+  test("number", () => {
+    // @ts-expect-error [ts2769] number is not a valid input type
+    isEmpty(2);
+  });
 
-  // @ts-expect-error [ts2769] boolean is not a valid input type
-  isEmpty(false);
+  test("boolean", () => {
+    // @ts-expect-error [ts2769] boolean is not a valid input type
+    isEmpty(false);
+  });
 
-  // @ts-expect-error [ts2769] null is not a valid input type
-  isEmpty(null);
+  test("null", () => {
+    // @ts-expect-error [ts2769] null is not a valid input type
+    isEmpty(null);
+  });
+
+  test("optional arrays", () => {
+    // @ts-expect-error [ts2769] undefined is only allowed with strings
+    isEmpty([] as ReadonlyArray<string> | undefined);
+  });
 
-  // @ts-expect-error [ts2769] undefined is only allowed with strings
-  isEmpty([] as ReadonlyArray<string> | undefined);
+  test("optional objects", () => {
+    // @ts-expect-error [ts2769] undefined is only allowed with strings
+    isEmpty({} as Record<string, string> | undefined);
+  });
+});
 
-  // @ts-expect-error [ts2769] undefined is only allowed with strings
-  isEmpty({} as Record<string, string> | undefined);
+describe("objects", () => {
+  test("infinite record", () => {
+    const data = {} as Record<string, string>;
+    if (isEmpty(data)) {
+      expectTypeOf(data).toEqualTypeOf<Record<string, never>>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<Record<string, string>>();
+    }
+  });
+
+  test("required record", () => {
+    const data = { a: 123, b: 456 } as Record<"a" | "b", number>;
+    if (isEmpty(data)) {
+      // @ts-expect-error [ts2554] -- This is a mistake, the type should be
+      // narrowed to never because the object can never be empty.
+      expectTypeOf(data).toEqualTypeOf<never>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<Record<"a" | "b", number>>();
+    }
+  });
+
+  test("partial record", () => {
+    const data = {} as Partial<Record<"a" | "b", number>>;
+    if (isEmpty(data)) {
+      expectTypeOf(data).toEqualTypeOf<Record<"a" | "b", never>>();
+    } else {
+      expectTypeOf(data).toEqualTypeOf<Partial<Record<"a" | "b", number>>>();
+    }
+  });
 });
 
-describe("strings are narrowed correctly", () => {
+describe("strings", () => {
   test("just undefined", () => {
     const data = undefined;
     if (isEmpty(data)) {
diff --git a/src/sample.test-d.ts b/src/sample.test-d.ts
index 06ab6b0..5531667 100644
--- a/src/sample.test-d.ts
+++ b/src/sample.test-d.ts
@@ -101,12 +101,7 @@ describe("sampleSize < n", () => {
     ];
     const result = sample(array, 4);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: This type is OK (it doesn't type things incorrectly) but it's
-      // not as narrow as it could be. If the tuple has only 2 elements, the
-      // result should be: `[number, boolean]` and similarly for 3 elements.
-      // Only when the tuple has 4 elements then the type of the first and
-      // second item should be loosened because we don't know how many items
-      // are in the input.
+      // TODO: This type is OK (it doesn't type things incorrectly) but it's not as narrow as it could be. If the tuple has only 2 elements, the result should be: `[number, boolean]` and similarly for 3 elements. Only when the tuple has 4 elements then the type of the first and second item should be loosened because we don't know how many items are in the input.
       | [boolean | number | string, boolean | string, string, string]
       | [boolean | number | string, boolean | string, string]
       | [boolean | number | string, boolean | string]
@@ -123,12 +118,7 @@ describe("sampleSize < n", () => {
     ];
     const result = sample(array, 4);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: This type is OK (it doesn't type things incorrectly) but it's
-      // not as narrow as it could be. If the tuple has only 2 elements, the
-      // result should be: `[number, boolean]` and similarly for 3 elements.
-      // Only when the tuple has 4 elements then the type of the first and
-      // second item should be loosened because we don't know how many items
-      // are in the input.
+      // TODO: This type is OK (it doesn't type things incorrectly) but it's not as narrow as it could be. If the tuple has only 2 elements, the result should be: `[number, boolean]` and similarly for 3 elements. Only when the tuple has 4 elements then the type of the first and second item should be loosened because we don't know how many items are in the input.
       | [boolean | number | string, boolean | string, string, string]
       | [boolean | number | string, boolean | string, string]
       | [boolean | number | string, boolean | string]
@@ -229,12 +219,7 @@ describe("sampleSize === n", () => {
     ];
     const result = sample(array, 5);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: This type is OK (it doesn't type things incorrectly) but it's
-      // not as narrow as it could be. If the tuple has only 2 elements, the
-      // result should be: `[number, boolean]` and similarly for 3 and 4
-      // elements. Only when the return tuple has 5 elements then the type of
-      // the first and second item should be loosened because we don't know
-      // how many items are in the input.
+      // TODO: This type is OK (it doesn't type things incorrectly) but it's not as narrow as it could be. If the tuple has only 2 elements, the result should be: `[number, boolean]` and similarly for 3 and 4 elements. Only when the return tuple has 5 elements then the type of the first and second item should be loosened because we don't know how many items are in the input.
       | [boolean | number | string, boolean | string, string, string, string]
       | [boolean | number | string, boolean | string, string, string]
       | [boolean | number | string, boolean | string, string]
@@ -252,12 +237,7 @@ describe("sampleSize === n", () => {
     ];
     const result = sample(array, 5);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: This type is OK (it doesn't type things incorrectly) but it's
-      // not as narrow as it could be. If the tuple has only 2 elements, the
-      // result should be: `[number, boolean]` and similarly for 3 and 4
-      // elements. Only when the return tuple has 5 elements then the type of
-      // the first and second item should be loosened because we don't know
-      // how many items are in the input.
+      // TODO: This type is OK (it doesn't type things incorrectly) but it's not as narrow as it could be. If the tuple has only 2 elements, the result should be: `[number, boolean]` and similarly for 3 and 4 elements. Only when the return tuple has 5 elements then the type of the first and second item should be loosened because we don't know how many items are in the input.
       | [boolean | number | string, boolean | string, string, string, string]
       | [boolean | number | string, boolean | string, string, string]
       | [boolean | number | string, boolean | string, string]
@@ -393,12 +373,7 @@ describe("sampleSize > n", () => {
     ];
     const result = sample(array, 10);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: This type is OK (it doesn't type things incorrectly) but it's
-      // not as narrow as it could be. If the tuple has only 2 elements, the
-      // result should be: `[number, boolean]` and similarly for 3-9 elements.
-      // Only when the return tuple has 10 elements then the type of the first
-      // and second item should be loosened because we don't know how many
-      // items are in the input.
+      // TODO: This type is OK (it doesn't type things incorrectly) but it's not as narrow as it could be. If the tuple has only 2 elements, the result should be: `[number, boolean]` and similarly for 3-9 elements. Only when the return tuple has 10 elements then the type of the first and second item should be loosened because we don't know how many items are in the input.
       | [
           boolean | number | string,
           boolean | string,
@@ -466,12 +441,7 @@ describe("sampleSize > n", () => {
     ];
     const result = sample(array, 10);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: This type is OK (it doesn't type things incorrectly) but it's
-      // not as narrow as it could be. If the tuple has only 2 elements, the
-      // result should be: `[number, boolean]` and similarly for 3-9 elements.
-      // Only when the return tuple has 10 elements then the type of the first
-      // and second item should be loosened because we don't know how many
-      // items are in the input.
+      // TODO: This type is OK (it doesn't type things incorrectly) but it's not as narrow as it could be. If the tuple has only 2 elements, the result should be: `[number, boolean]` and similarly for 3-9 elements. Only when the return tuple has 10 elements then the type of the first and second item should be loosened because we don't know how many items are in the input.
       | [
           boolean | number | string,
           boolean | string,
@@ -763,8 +733,7 @@ describe("non-const sampleSize", () => {
     ];
     const result = sample(array, 5 as number);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: the typing isn't ideal here. I'm not even sure what the type
-      // here should be...
+      // TODO: the typing isn't ideal here. I'm not even sure what the type here should be...
       Array<boolean | number | string>
     >();
   });
@@ -779,8 +748,7 @@ describe("non-const sampleSize", () => {
     ];
     const result = sample(array, 5 as number);
     expectTypeOf(result).toEqualTypeOf<
-      // TODO: the typing isn't ideal here. I'm not even sure what the type
-      // here should be...
+      // TODO: the typing isn't ideal here. I'm not even sure what the type here should be...
       Array<boolean | number | string>
     >();
   });