From fdb1d16ef0d26ad1b18a5e491af4aafec668cb4f Mon Sep 17 00:00:00 2001
From: Josh Goldberg <git@joshuakgoldberg.com>
Date: Thu, 16 Jan 2025 08:12:33 -0500
Subject: [PATCH 1/2] feat: add redux-toolkit-floating-promises example

---
 package-lock.json                             | 92 ++++++++++++++++++-
 .../redux-toolkit-floating-promises/README.md | 25 +++++
 .../eslint.config.js                          | 27 ++++++
 .../redux-toolkit-floating-promises/index.ts  | 16 ++++
 .../package.json                              | 26 ++++++
 .../tsconfig.json                             |  8 ++
 6 files changed, 191 insertions(+), 3 deletions(-)
 create mode 100644 packages/redux-toolkit-floating-promises/README.md
 create mode 100644 packages/redux-toolkit-floating-promises/eslint.config.js
 create mode 100644 packages/redux-toolkit-floating-promises/index.ts
 create mode 100644 packages/redux-toolkit-floating-promises/package.json
 create mode 100644 packages/redux-toolkit-floating-promises/tsconfig.json

diff --git a/package-lock.json b/package-lock.json
index cef5231..bbeed6c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -703,6 +703,30 @@
         "node": ">= 8"
       }
     },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
+      "integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==",
+      "license": "MIT",
+      "dependencies": {
+        "immer": "^10.0.3",
+        "redux": "^5.0.1",
+        "redux-thunk": "^3.1.0",
+        "reselect": "^5.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.28.1",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
@@ -1004,9 +1028,9 @@
       "license": "MIT"
     },
     "node_modules/@types/node": {
-      "version": "22.10.2",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
-      "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
+      "version": "22.10.7",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+      "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -2128,6 +2152,16 @@
         "node": ">= 4"
       }
     },
+    "node_modules/immer": {
+      "version": "10.1.1",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+      "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/immer"
+      }
+    },
     "node_modules/import-fresh": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -2406,6 +2440,10 @@
         "tslib": "^2.0.3"
       }
     },
+    "node_modules/node-test-floating-promises": {
+      "resolved": "packages/node-test-floating-promises",
+      "link": true
+    },
     "node_modules/optionator": {
       "version": "0.9.3",
       "license": "MIT",
@@ -2618,6 +2656,25 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/redux": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+      "license": "MIT"
+    },
+    "node_modules/redux-thunk": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+      "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "redux": "^5.0.0"
+      }
+    },
+    "node_modules/redux-toolkit-floating-promises": {
+      "resolved": "packages/redux-toolkit-floating-promises",
+      "link": true
+    },
     "node_modules/require-from-string": {
       "version": "2.0.2",
       "dev": true,
@@ -2626,6 +2683,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/reselect": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+      "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+      "license": "MIT"
+    },
     "node_modules/resolve-from": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3664,6 +3727,29 @@
         "typescript-eslint": "^8.18.0"
       }
     },
+    "packages/node-test-floating-promises": {
+      "version": "0.0.0",
+      "license": "MIT",
+      "devDependencies": {
+        "@types/node": "^22.10.7",
+        "eslint": "^9.16.0",
+        "typescript": "^5.7.2",
+        "typescript-eslint": "^8.18.0"
+      }
+    },
+    "packages/redux-toolkit-floating-promises": {
+      "version": "0.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "@reduxjs/toolkit": "2.5.0"
+      },
+      "devDependencies": {
+        "@types/node": "^22.10.7",
+        "eslint": "^9.16.0",
+        "typescript": "^5.7.2",
+        "typescript-eslint": "^8.18.0"
+      }
+    },
     "packages/typed-rule-via-linter": {
       "version": "0.0.0",
       "license": "MIT",
diff --git a/packages/redux-toolkit-floating-promises/README.md b/packages/redux-toolkit-floating-promises/README.md
new file mode 100644
index 0000000..8b89e63
--- /dev/null
+++ b/packages/redux-toolkit-floating-promises/README.md
@@ -0,0 +1,25 @@
+# Example: `redux-toolkit` and Floating Promise Detection
+
+An example of using [`redux-toolkit`](https://redux-toolkit.js.org) along with the [`@typescript-eslint/no-floating-promises` rule](https://typescript-eslint.io/rules/no-floating-promises) enabled.
+It uses the [`allowForKnownSafePromises` rule option](https://typescript-eslint.io/rules/no-floating-promises/#allowforknownsafepromises) to not report on RTK APIs that create a `SafePromise`, such as `createAsyncThunk`.
+
+## Setup
+
+```shell
+npm i
+```
+
+## Usage
+
+```shell
+npm run lint
+```
+
+There should be no lint reports.
+
+If you remove the `allowForKnownSafePromises` option from `eslint.config.js`, there will be:
+
+```plaintext
+.../index.test.ts
+  4:1  error  Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator  @typescript-eslint/no-floating-promises
+```
\ No newline at end of file
diff --git a/packages/redux-toolkit-floating-promises/eslint.config.js b/packages/redux-toolkit-floating-promises/eslint.config.js
new file mode 100644
index 0000000..cdd6d87
--- /dev/null
+++ b/packages/redux-toolkit-floating-promises/eslint.config.js
@@ -0,0 +1,27 @@
+// @ts-check
+
+import eslint from "@eslint/js";
+import tseslint from "typescript-eslint";
+
+export default tseslint.config(
+  eslint.configs.recommended,
+  tseslint.configs.recommendedTypeChecked,
+  {
+    languageOptions: {
+      parserOptions: {
+        projectService: {
+          allowDefaultProject: ['*.config.*']
+        },
+      },
+    },
+    rules: {
+      "@typescript-eslint/no-floating-promises": [
+        "error", {
+          "allowForKnownSafePromises": [
+            { "from": "package", "name": "SafePromise", "package": "@reduxjs/toolkit" }
+          ]
+        }
+      ]
+    }
+  }
+);
diff --git a/packages/redux-toolkit-floating-promises/index.ts b/packages/redux-toolkit-floating-promises/index.ts
new file mode 100644
index 0000000..81eba26
--- /dev/null
+++ b/packages/redux-toolkit-floating-promises/index.ts
@@ -0,0 +1,16 @@
+import { configureStore, createAsyncThunk } from '@reduxjs/toolkit';
+
+function reducer() {
+    // ...
+}
+
+const store = configureStore({ reducer });
+
+const exampleThunk = createAsyncThunk(
+  'example',
+  async () => {
+    // ...
+  },
+);
+
+store.dispatch(exampleThunk());
diff --git a/packages/redux-toolkit-floating-promises/package.json b/packages/redux-toolkit-floating-promises/package.json
new file mode 100644
index 0000000..fc0a59d
--- /dev/null
+++ b/packages/redux-toolkit-floating-promises/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "redux-toolkit-floating-promises",
+  "version": "0.0.0",
+  "description": "Example of using `redux-toolkit` with `@typescript-eslint/no-floating-promises`'s `allowForKnownSafePromises` option.",
+  "main": "index.ts",
+  "repository": {
+    "directory": "packages/redux-toolkit-floating-promises",
+    "type": "git",
+    "url": "https://github.com/typescript-eslint/typescript-eslint-examples"
+  },
+  "license": "MIT",
+  "devDependencies": {
+    "@types/node": "^22.10.7",
+    "eslint": "^9.16.0",
+    "typescript": "^5.7.2",
+    "typescript-eslint": "^8.18.0"
+  },
+  "dependencies": {
+    "@reduxjs/toolkit": "2.5.0"
+  },
+  "scripts": {
+    "lint": "eslint .",
+    "tsc": "tsc"
+  },
+  "type": "module"
+}
diff --git a/packages/redux-toolkit-floating-promises/tsconfig.json b/packages/redux-toolkit-floating-promises/tsconfig.json
new file mode 100644
index 0000000..5283fbc
--- /dev/null
+++ b/packages/redux-toolkit-floating-promises/tsconfig.json
@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
+    "noEmit": true,
+    "strict": true
+  },
+}

From 185fa5c6d3d9dfa6bcaa876f40195a36f0e348fe Mon Sep 17 00:00:00 2001
From: Josh Goldberg <git@joshuakgoldberg.com>
Date: Fri, 17 Jan 2025 08:04:29 -0500
Subject: [PATCH 2/2] Add in two more rtk package allows too

---
 packages/redux-toolkit-floating-promises/eslint.config.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/redux-toolkit-floating-promises/eslint.config.js b/packages/redux-toolkit-floating-promises/eslint.config.js
index cdd6d87..b418e95 100644
--- a/packages/redux-toolkit-floating-promises/eslint.config.js
+++ b/packages/redux-toolkit-floating-promises/eslint.config.js
@@ -18,7 +18,9 @@ export default tseslint.config(
       "@typescript-eslint/no-floating-promises": [
         "error", {
           "allowForKnownSafePromises": [
-            { "from": "package", "name": "SafePromise", "package": "@reduxjs/toolkit" }
+            { from: "package", name: "SafePromise", package: "@reduxjs/toolkit" },
+            { from: 'package', name: 'SafePromise', package: '@reduxjs/toolkit/query' },
+            { from: 'package', name: 'SafePromise', package: '@reduxjs/toolkit/query/react' },
           ]
         }
       ]