diff --git a/.github/workflows/verifications.yml b/.github/workflows/verifications.yml
index 92d1b2a4..ccb2b425 100644
--- a/.github/workflows/verifications.yml
+++ b/.github/workflows/verifications.yml
@@ -31,11 +31,26 @@ jobs:
   tests:
     name: Tests (Node v${{ matrix.node }} - ESLint v${{ matrix.eslint }})
     runs-on: ubuntu-latest
+    timeout-minutes: 3
     strategy:
       fail-fast: false
       matrix:
         node: [12.22.0, 12, 14.17.0, 14, 16, 17, 18, 19, 20, 22]
-        eslint: [7.5, 7, 8]
+        eslint: [7.5, 7, 8, 9]
+        exclude:
+          # eslint@9 doesn't support < Node v18
+          - node: 17
+            eslint: 9
+          - node: 16
+            eslint: 9
+          - node: 14
+            eslint: 9
+          - node: 14.17.0
+            eslint: 9
+          - node: 12
+            eslint: 9
+          - node: 12.22.0
+            eslint: 9
     steps:
       - name: Checkout
         uses: actions/checkout@v4
@@ -49,6 +64,10 @@ jobs:
       - name: Install dependencies
         run: npm install
 
+        # see https://github.com/npm/cli/issues/7349
+      - if: ${{ matrix.eslint == 9 }}
+        run: npm un @typescript-eslint/eslint-plugin eslint-plugin-jest eslint-doc-generator
+
       - name: Install ESLint v${{ matrix.eslint }}
         run: npm install --no-save --force eslint@${{ matrix.eslint }}
 
diff --git a/package-lock.json b/package-lock.json
index a01dd55f..d6176697 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,6 +42,7 @@
 				"npm-run-all2": "^5.0.2",
 				"prettier": "^3.3.3",
 				"semantic-release": "^19.0.5",
+				"semver": "^7.6.3",
 				"ts-node": "^10.9.2",
 				"typescript": "5.0.4"
 			},
@@ -118,6 +119,16 @@
 				"url": "https://opencollective.com/babel"
 			}
 		},
+		"node_modules/@babel/core/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/@babel/eslint-parser": {
 			"version": "7.25.8",
 			"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.8.tgz",
@@ -136,6 +147,16 @@
 				"eslint": "^7.5.0 || ^8.0.0 || ^9.0.0"
 			}
 		},
+		"node_modules/@babel/eslint-parser/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/@babel/eslint-plugin": {
 			"version": "7.25.7",
 			"resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.25.7.tgz",
@@ -183,6 +204,16 @@
 				"node": ">=6.9.0"
 			}
 		},
+		"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/@babel/helper-module-imports": {
 			"version": "7.25.7",
 			"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz",
@@ -3109,18 +3140,6 @@
 				"node": ">=6"
 			}
 		},
-		"node_modules/@semantic-release/npm/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/@semantic-release/release-notes-generator": {
 			"version": "10.0.3",
 			"resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz",
@@ -3650,18 +3669,6 @@
 				}
 			}
 		},
-		"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/@typescript-eslint/parser": {
 			"version": "5.62.0",
 			"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
@@ -3770,17 +3777,6 @@
 				}
 			}
 		},
-		"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/@typescript-eslint/utils": {
 			"version": "5.62.0",
 			"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
@@ -3806,17 +3802,6 @@
 				"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
 			}
 		},
-		"node_modules/@typescript-eslint/utils/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/@typescript-eslint/visitor-keys": {
 			"version": "5.62.0",
 			"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
@@ -4861,6 +4846,16 @@
 				"node": ">=10"
 			}
 		},
+		"node_modules/conventional-changelog-writer/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/conventional-commits-filter": {
 			"version": "2.0.7",
 			"resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz",
@@ -5977,6 +5972,16 @@
 				"node": ">=0.10.0"
 			}
 		},
+		"node_modules/eslint-plugin-import/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/eslint-plugin-jest": {
 			"version": "27.9.0",
 			"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz",
@@ -6034,6 +6039,16 @@
 				"eslint": ">=5.16.0"
 			}
 		},
+		"node_modules/eslint-plugin-node/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/eslint-plugin-promise": {
 			"version": "6.6.0",
 			"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz",
@@ -7731,18 +7746,6 @@
 				"semver": "^7.6.3"
 			}
 		},
-		"node_modules/is-bun-module/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/is-callable": {
 			"version": "1.2.7",
 			"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -8099,6 +8102,16 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/istanbul-lib-instrument/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/istanbul-lib-report": {
 			"version": "3.0.1",
 			"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
@@ -10397,18 +10410,6 @@
 			"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
 			"dev": true
 		},
-		"node_modules/jest-snapshot/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/jest-snapshot/node_modules/supports-color": {
 			"version": "7.2.0",
 			"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -11874,18 +11875,6 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/make-dir/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/make-error": {
 			"version": "1.3.6",
 			"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -12218,18 +12207,6 @@
 				"node": ">=10"
 			}
 		},
-		"node_modules/normalize-package-data/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/normalize-path": {
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -16131,18 +16108,6 @@
 				"node": ">=10"
 			}
 		},
-		"node_modules/semantic-release/node_modules/semver": {
-			"version": "7.6.3",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-			"dev": true,
-			"bin": {
-				"semver": "bin/semver.js"
-			},
-			"engines": {
-				"node": ">=10"
-			}
-		},
 		"node_modules/semantic-release/node_modules/wrap-ansi": {
 			"version": "7.0.0",
 			"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -16188,12 +16153,15 @@
 			}
 		},
 		"node_modules/semver": {
-			"version": "6.3.1",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-			"dev": true,
+			"version": "7.6.3",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+			"license": "ISC",
 			"bin": {
 				"semver": "bin/semver.js"
+			},
+			"engines": {
+				"node": ">=10"
 			}
 		},
 		"node_modules/semver-diff": {
@@ -16208,6 +16176,16 @@
 				"node": ">=8"
 			}
 		},
+		"node_modules/semver-diff/node_modules/semver": {
+			"version": "6.3.1",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+			"dev": true,
+			"license": "ISC",
+			"bin": {
+				"semver": "bin/semver.js"
+			}
+		},
 		"node_modules/semver-regex": {
 			"version": "3.1.4",
 			"resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz",
@@ -17699,6 +17677,14 @@
 				"gensync": "^1.0.0-beta.2",
 				"json5": "^2.2.3",
 				"semver": "^6.3.1"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"@babel/eslint-parser": {
@@ -17710,6 +17696,14 @@
 				"@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
 				"eslint-visitor-keys": "^2.1.0",
 				"semver": "^6.3.1"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"@babel/eslint-plugin": {
@@ -17744,6 +17738,14 @@
 				"browserslist": "^4.24.0",
 				"lru-cache": "^5.1.1",
 				"semver": "^6.3.1"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"@babel/helper-module-imports": {
@@ -19962,12 +19964,6 @@
 					"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
 					"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
 					"dev": true
-				},
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
 				}
 			}
 		},
@@ -20355,14 +20351,6 @@
 				"natural-compare-lite": "^1.4.0",
 				"semver": "^7.3.7",
 				"tsutils": "^3.21.0"
-			},
-			"dependencies": {
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
-				}
 			}
 		},
 		"@typescript-eslint/parser": {
@@ -20415,13 +20403,6 @@
 				"is-glob": "^4.0.3",
 				"semver": "^7.3.7",
 				"tsutils": "^3.21.0"
-			},
-			"dependencies": {
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
-				}
 			}
 		},
 		"@typescript-eslint/utils": {
@@ -20437,13 +20418,6 @@
 				"@typescript-eslint/typescript-estree": "5.62.0",
 				"eslint-scope": "^5.1.1",
 				"semver": "^7.3.7"
-			},
-			"dependencies": {
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
-				}
 			}
 		},
 		"@typescript-eslint/visitor-keys": {
@@ -21189,6 +21163,14 @@
 				"semver": "^6.0.0",
 				"split": "^1.0.0",
 				"through2": "^4.0.0"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"conventional-commits-filter": {
@@ -22086,6 +22068,12 @@
 					"requires": {
 						"esutils": "^2.0.2"
 					}
+				},
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
 				}
 			}
 		},
@@ -22117,6 +22105,14 @@
 				"minimatch": "^3.0.4",
 				"resolve": "^1.10.1",
 				"semver": "^6.1.0"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"eslint-plugin-promise": {
@@ -23218,14 +23214,6 @@
 			"dev": true,
 			"requires": {
 				"semver": "^7.6.3"
-			},
-			"dependencies": {
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
-				}
 			}
 		},
 		"is-callable": {
@@ -23456,6 +23444,14 @@
 				"@istanbuljs/schema": "^0.1.2",
 				"istanbul-lib-coverage": "^3.2.0",
 				"semver": "^6.3.0"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"istanbul-lib-report": {
@@ -25283,12 +25279,6 @@
 					"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
 					"dev": true
 				},
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
-				},
 				"supports-color": {
 					"version": "7.2.0",
 					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -26277,14 +26267,6 @@
 			"dev": true,
 			"requires": {
 				"semver": "^7.5.3"
-			},
-			"dependencies": {
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
-				}
 			}
 		},
 		"make-error": {
@@ -26528,14 +26510,6 @@
 				"is-core-module": "^2.5.0",
 				"semver": "^7.3.4",
 				"validate-npm-package-license": "^3.0.1"
-			},
-			"dependencies": {
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
-				}
 			}
 		},
 		"normalize-path": {
@@ -29274,12 +29248,6 @@
 						"yaml": "^1.10.0"
 					}
 				},
-				"semver": {
-					"version": "7.6.3",
-					"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
-					"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
-					"dev": true
-				},
 				"wrap-ansi": {
 					"version": "7.0.0",
 					"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -29315,10 +29283,9 @@
 			}
 		},
 		"semver": {
-			"version": "6.3.1",
-			"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-			"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-			"dev": true
+			"version": "7.6.3",
+			"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+			"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
 		},
 		"semver-diff": {
 			"version": "3.1.1",
@@ -29327,6 +29294,14 @@
 			"dev": true,
 			"requires": {
 				"semver": "^6.3.0"
+			},
+			"dependencies": {
+				"semver": {
+					"version": "6.3.1",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+					"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+					"dev": true
+				}
 			}
 		},
 		"semver-regex": {
diff --git a/package.json b/package.json
index 7f5159fc..8bfa55a2 100644
--- a/package.json
+++ b/package.json
@@ -84,6 +84,7 @@
 		"npm-run-all2": "^5.0.2",
 		"prettier": "^3.3.3",
 		"semantic-release": "^19.0.5",
+		"semver": "^7.6.3",
 		"ts-node": "^10.9.2",
 		"typescript": "5.0.4"
 	},
diff --git a/tests/lib/FlatCompatRuleTester.ts b/tests/lib/FlatCompatRuleTester.ts
new file mode 100644
index 00000000..b3ef5943
--- /dev/null
+++ b/tests/lib/FlatCompatRuleTester.ts
@@ -0,0 +1,191 @@
+import { TSESLint } from '@typescript-eslint/utils';
+import { version as eslintVersion } from 'eslint/package.json';
+import * as semver from 'semver';
+
+export const usingFlatConfig = semver.major(eslintVersion) >= 9;
+
+declare module '@typescript-eslint/utils/dist/ts-eslint' {
+	// eslint-disable-next-line @typescript-eslint/no-namespace
+	export namespace FlatConfig {
+		export interface LinterOptions {
+			/**
+			 * A Boolean value indicating if inline configuration is allowed.
+			 */
+			noInlineConfig?: boolean;
+			/**
+			 * A severity string indicating if and how unused disable and enable
+			 * directives should be tracked and reported. For legacy compatibility, `true`
+			 * is equivalent to `"warn"` and `false` is equivalent to `"off"`.
+			 * @default "off"
+			 */
+			reportUnusedDisableDirectives?:
+				| TSESLint.Linter.Severity
+				| TSESLint.Linter.SeverityString
+				| boolean;
+		}
+
+		export interface Config {
+			/**
+			 * An string to identify the configuration object. Used in error messages and inspection tools.
+			 */
+			name?: string;
+			/**
+			 * An array of glob patterns indicating the files that the configuration object should apply to.
+			 * If not specified, the configuration object applies to all files matched by any other configuration object.
+			 */
+			files?: (string | string[])[];
+			/**
+			 * An array of glob patterns indicating the files that the configuration object should not apply to.
+			 * If not specified, the configuration object applies to all files matched by files.
+			 */
+			ignores?: string[];
+			/**
+			 * An object containing settings related to how JavaScript is configured for linting.
+			 */
+			languageOptions?: LanguageOptions;
+			/**
+			 * An object containing settings related to the linting process.
+			 */
+			linterOptions?: LinterOptions;
+			/**
+			 * An object containing a name-value mapping of plugin names to plugin objects.
+			 * When `files` is specified, these plugins are only available to the matching files.
+			 */
+			plugins?: unknown;
+			/**
+			 * Either an object containing `preprocess()` and `postprocess()` methods or
+			 * a string indicating the name of a processor inside of a plugin
+			 * (i.e., `"pluginName/processorName"`).
+			 */
+			processor?: string | TSESLint.Linter.Processor;
+			/**
+			 * An object containing the configured rules.
+			 * When `files` or `ignores` are specified, these rule configurations are only available to the matching files.
+			 */
+			rules?: TSESLint.Linter.RulesRecord;
+			/**
+			 * An object containing name-value pairs of information that should be available to all rules.
+			 */
+			settings?: TSESLint.SharedConfigurationSettings;
+		}
+
+		export type ParserOptions = TSESLint.Linter.ParserOptions;
+
+		export interface LanguageOptions {
+			/**
+			 * The version of ECMAScript to support.
+			 * May be any year (i.e., `2022`) or version (i.e., `5`).
+			 * Set to `"latest"` for the most recent supported version.
+			 * @default "latest"
+			 */
+			ecmaVersion?: Required<TSESLint.ParserOptions>['ecmaVersion'];
+			/**
+			 * An object specifying additional objects that should be added to the global scope during linting.
+			 */
+			globals?:
+				| Record<string, 'readonly' | 'writable' | 'off' | true>
+				| undefined;
+			/**
+			 * An object containing a `parse()` method or a `parseForESLint()` method.
+			 * @default
+			 * ```
+			 * // https://github.com/eslint/espree
+			 * require('espree')
+			 * ```
+			 */
+			parser?: unknown;
+			/**
+			 * An object specifying additional options that are passed directly to the parser.
+			 * The available options are parser-dependent.
+			 */
+			parserOptions?: ParserOptions | undefined;
+			/**
+			 * The type of JavaScript source code.
+			 * Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files.
+			 * @default
+			 * ```
+			 * // for `.js` and `.mjs` files
+			 * "module"
+			 * // for `.cjs` files
+			 * "commonjs"
+			 * ```
+			 */
+			sourceType?: Required<TSESLint.ParserOptions>['sourceType'];
+		}
+	}
+}
+
+export class FlatCompatRuleTester extends TSESLint.RuleTester {
+	public constructor(testerConfig?: TSESLint.RuleTesterConfig) {
+		super(FlatCompatRuleTester._flatCompat(testerConfig));
+	}
+
+	public override run<
+		TMessageIds extends string,
+		TOptions extends readonly unknown[],
+	>(
+		ruleName: string,
+		rule: TSESLint.RuleModule<TMessageIds, TOptions>,
+		tests: TSESLint.RunTests<TMessageIds, TOptions>
+	) {
+		super.run(ruleName, rule, {
+			valid: tests.valid.map((t) => FlatCompatRuleTester._flatCompat(t)),
+			invalid: tests.invalid.map((t) => FlatCompatRuleTester._flatCompat(t)),
+		});
+	}
+
+	/* istanbul ignore next */
+	private static _flatCompat<
+		T extends
+			| undefined
+			| TSESLint.RuleTesterConfig
+			| string
+			| TSESLint.ValidTestCase<unknown[]>
+			| TSESLint.InvalidTestCase<string, unknown[]>,
+	>(config: T): T {
+		if (!config || !usingFlatConfig || typeof config === 'string') {
+			return config;
+		}
+
+		const obj: TSESLint.FlatConfig.Config & {
+			languageOptions: TSESLint.FlatConfig.LanguageOptions & {
+				parserOptions: TSESLint.FlatConfig.ParserOptions;
+			};
+		} = {
+			languageOptions: { parserOptions: {} },
+		};
+
+		for (const [key, value] of Object.entries(config)) {
+			if (key === 'parser') {
+				obj.languageOptions.parser = require(value as string);
+
+				continue;
+			}
+
+			if (key === 'parserOptions') {
+				for (const [option, val] of Object.entries(
+					value as { [s: string]: unknown }
+				)) {
+					if (option === 'ecmaVersion' || option === 'sourceType') {
+						// @ts-expect-error: TS thinks the value could the opposite type of whatever option is
+						obj.languageOptions[option] =
+							val as TSESLint.FlatConfig.LanguageOptions[
+								| 'ecmaVersion'
+								| 'sourceType'];
+
+						continue;
+					}
+
+					obj.languageOptions.parserOptions[option] = val;
+				}
+
+				continue;
+			}
+
+			// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+			obj[key as keyof typeof obj] = value;
+		}
+
+		return obj as unknown as T;
+	}
+}
diff --git a/tests/lib/rules/await-async-events.test.ts b/tests/lib/rules/await-async-events.test.ts
index 82a9c347..80ffb15d 100644
--- a/tests/lib/rules/await-async-events.test.ts
+++ b/tests/lib/rules/await-async-events.test.ts
@@ -131,7 +131,7 @@ ruleTester.run(RULE_NAME, rule, {
             doSomething()
             return fireEvent.${eventMethod}(getByLabelText('username'))
           }
-  
+
           await triggerEvent()
         })
         `,
@@ -142,7 +142,7 @@ ruleTester.run(RULE_NAME, rule, {
 					'testing-library/utils-module': 'test-utils',
 				},
 				code: `
-        import { fireEvent } from 'somewhere-else'
+        import { fireEvent } from 'somewhere-else' // not using ${testingFramework}
         test('unhandled promise from event not related to TL is valid', async () => {
           fireEvent.${eventMethod}(getByLabelText('username'))
         })
@@ -154,7 +154,7 @@ ruleTester.run(RULE_NAME, rule, {
 					'testing-library/utils-module': 'test-utils',
 				},
 				code: `
-        import { fireEvent } from 'test-utils'
+        import { fireEvent } from 'test-utils' // implicitly using ${testingFramework}
         test('await promise from event method imported from custom module is valid', async () => {
           await fireEvent.${eventMethod}(getByLabelText('username'))
         })
@@ -166,13 +166,13 @@ ruleTester.run(RULE_NAME, rule, {
 				// valid use case without call expression
 				// so there is no innermost function scope found
 				code: `
-        import { fireEvent } from 'test-utils'
+        import { fireEvent } from 'test-utils' // implicitly using ${testingFramework}
         test('edge case for innermost function without call expression', async () => {
           function triggerEvent() {
               doSomething()
               return fireEvent.focus(getByLabelText('username'))
             }
-    
+
           const reassignedFunction = triggerEvent
         })
         `,
@@ -318,7 +318,7 @@ ruleTester.run(RULE_NAME, rule, {
             doSomething()
             return userEvent.${eventMethod}(getByLabelText('username'))
           }
-  
+
           await triggerEvent()
         })
         `,
@@ -380,7 +380,7 @@ ruleTester.run(RULE_NAME, rule, {
               doSomething()
               return userEvent.focus(getByLabelText('username'))
             }
-    
+
           const reassignedFunction = triggerEvent
         })
         `,
@@ -447,11 +447,7 @@ ruleTester.run(RULE_NAME, rule, {
 							},
 						],
 						options: [{ eventModule: 'fireEvent' }],
-						output: `
-      import { fireEvent } from '${testingFramework}'
-
-      fireEvent.${eventMethod}(getByLabelText('username'))
-      `,
+						output: null,
 					}) as const
 			),
 			...FIRE_EVENT_ASYNC_FUNCTIONS.map(
@@ -611,7 +607,7 @@ ruleTester.run(RULE_NAME, rule, {
 							'testing-library/utils-module': 'test-utils',
 						},
 						code: `
-      import { fireEvent } from 'test-utils'
+      import { fireEvent } from 'test-utils' // implicitly using ${testingFramework}
       test(
       'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid',
       () => {
@@ -628,7 +624,7 @@ ruleTester.run(RULE_NAME, rule, {
 						],
 						options: [{ eventModule: 'fireEvent' }],
 						output: `
-      import { fireEvent } from 'test-utils'
+      import { fireEvent } from 'test-utils' // implicitly using ${testingFramework}
       test(
       'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid',
       async () => {
@@ -759,16 +755,7 @@ ruleTester.run(RULE_NAME, rule, {
 							},
 						],
 						options: [{ eventModule: 'fireEvent' }],
-						output: `
-      import { fireEvent } from '${testingFramework}'
-
-      function triggerEvent() {
-        doSomething()
-        return fireEvent.${eventMethod}(getByLabelText('username'))
-      }
-
-      triggerEvent()
-      `,
+						output: null,
 					}) as const
 			),
 		]),
@@ -805,7 +792,7 @@ ruleTester.run(RULE_NAME, rule, {
 					({
 						code: `
       import userEvent from '${testingFramework}'
-			
+
       userEvent.${eventMethod}(getByLabelText('username'))
       `,
 						errors: [
@@ -818,11 +805,7 @@ ruleTester.run(RULE_NAME, rule, {
 							},
 						],
 						options: [{ eventModule: 'userEvent' }],
-						output: `
-      import userEvent from '${testingFramework}'
-			
-      userEvent.${eventMethod}(getByLabelText('username'))
-      `,
+						output: null,
 					}) as const
 			),
 			...USER_EVENT_ASYNC_FUNCTIONS.map(
@@ -974,16 +957,7 @@ ruleTester.run(RULE_NAME, rule, {
 							},
 						],
 						options: [{ eventModule: 'userEvent' }],
-						output: `
-      import userEvent from '${testingFramework}'
-
-      function triggerEvent() {
-        doSomething()
-        return userEvent.${eventMethod}(getByLabelText('username'))
-      }
-
-      triggerEvent()
-      `,
+						output: null,
 					}) as const
 			),
 			...USER_EVENT_ASYNC_FUNCTIONS.map(
diff --git a/tests/lib/rules/await-async-utils.test.ts b/tests/lib/rules/await-async-utils.test.ts
index 1a5490c9..fc538079 100644
--- a/tests/lib/rules/await-async-utils.test.ts
+++ b/tests/lib/rules/await-async-utils.test.ts
@@ -113,9 +113,9 @@ ruleTester.run(RULE_NAME, rule, {
             const aPromise =  ${asyncUtil}(() =>
               document.querySelector('div.getOuttaHere')
             );
-            
+
             doSomethingElse();
-            
+
             return aPromise;
           };
         });
@@ -126,7 +126,7 @@ ruleTester.run(RULE_NAME, rule, {
 				'testing-library/utils-module': 'test-utils',
 			},
 			code: `
-        import { ${asyncUtil} } from 'some-other-library';
+        import { ${asyncUtil} } from 'some-other-library'; // rather than ${testingFramework}
         test(
         'aggressive reporting disabled - util "${asyncUtil}" which is not related to testing library is valid',
         async () => {
@@ -140,7 +140,7 @@ ruleTester.run(RULE_NAME, rule, {
 				'testing-library/utils-module': 'test-utils',
 			},
 			code: `
-        import * as asyncUtils from 'some-other-library';
+        import * as asyncUtils from 'some-other-library'; // rather than ${testingFramework}
         test(
         'aggressive reporting disabled - util "asyncUtils.${asyncUtil}" which is not related to testing library is valid',
         async () => {
@@ -228,7 +228,7 @@ ruleTester.run(RULE_NAME, rule, {
 		...ASYNC_UTILS.map((asyncUtil) => ({
 			code: `
         import { ${asyncUtil} } from '${testingFramework}';
-        
+
         function waitForSomethingAsync() {
           return ${asyncUtil}(() => somethingAsync())
         }
@@ -242,7 +242,7 @@ ruleTester.run(RULE_NAME, rule, {
 			code: `
       test('using unrelated promises with Promise.all is valid', async () => {
         Promise.all([
-          waitForNotRelatedToTestingLibrary(),
+          waitForNotRelatedToTestingLibrary(), // completely unrelated to ${testingFramework}
           promise1,
           await foo().then(() => baz())
         ])
@@ -262,13 +262,14 @@ ruleTester.run(RULE_NAME, rule, {
 		},
 		...ASYNC_UTILS.map((asyncUtil) => ({
 			code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -286,7 +287,7 @@ ruleTester.run(RULE_NAME, rule, {
 
           const { waitForAsyncUtil: myDestructuredAlias } = setup();
           await myDestructuredAlias();
-          
+
           const { user, ...rest } = setup();
           await rest.waitForAsyncUtil();
 
@@ -297,24 +298,24 @@ ruleTester.run(RULE_NAME, rule, {
 		...ASYNC_UTILS.map((asyncUtil) => ({
 			code: `
           import React from 'react';
-          import { render, act } from '@testing-library/react';
-          
+          import { render, act } from '${testingFramework}';
+
           const doWithAct = async (timeout) => {
             await act(async () => await ${asyncUtil}(screen.getByTestId('my-test')));
           };
-          
+
           describe('Component', () => {
             const mock = jest.fn();
-          
+
             it('test', async () => {
               let Component = () => {
                 mock(1);
                 return <div />;
               };
               render(<Component />);
-          
+
               await doWithAct(500);
-          
+
               const myNumberTestVar = 1;
               const myBooleanTestVar = false;
               const myArrayTestVar = [1, 2];
@@ -467,7 +468,7 @@ ruleTester.run(RULE_NAME, rule, {
 				({
 					code: `
         import { ${asyncUtil}, render } from '${testingFramework}';
-        
+
         function waitForSomethingAsync() {
           return ${asyncUtil}(() => somethingAsync())
         }
@@ -491,7 +492,7 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
-        import { ${asyncUtil} } from 'some-other-library';
+        import { ${asyncUtil} } from 'some-other-library'; // rather than ${testingFramework}
         test(
         'aggressive reporting - util "${asyncUtil}" which is not related to testing library is invalid',
         async () => {
@@ -514,7 +515,7 @@ ruleTester.run(RULE_NAME, rule, {
 				({
 					code: `
         import { ${asyncUtil}, render } from '${testingFramework}';
-        
+
         function waitForSomethingAsync() {
           return ${asyncUtil}(() => somethingAsync())
         }
@@ -539,7 +540,7 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
-        import * as asyncUtils from 'some-other-library';
+        import * as asyncUtils from 'some-other-library'; // rather than ${testingFramework}
         test(
         'aggressive reporting - util "asyncUtils.${asyncUtil}" which is not related to testing library is invalid',
         async () => {
@@ -561,13 +562,14 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -578,7 +580,7 @@ ruleTester.run(RULE_NAME, rule, {
       `,
 					errors: [
 						{
-							line: 14,
+							line: 15,
 							column: 11,
 							messageId: 'asyncUtilWrapper',
 							data: { name: 'waitForAsyncUtil' },
@@ -590,13 +592,14 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -608,7 +611,7 @@ ruleTester.run(RULE_NAME, rule, {
       `,
 					errors: [
 						{
-							line: 15,
+							line: 16,
 							column: 11,
 							messageId: 'asyncUtilWrapper',
 							data: { name: 'myAlias' },
@@ -620,13 +623,14 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -637,7 +641,7 @@ ruleTester.run(RULE_NAME, rule, {
       `,
 					errors: [
 						{
-							line: 14,
+							line: 15,
 							column: 17,
 							messageId: 'asyncUtilWrapper',
 							data: { name: 'waitForAsyncUtil' },
@@ -649,13 +653,14 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -666,7 +671,7 @@ ruleTester.run(RULE_NAME, rule, {
       `,
 					errors: [
 						{
-							line: 14,
+							line: 15,
 							column: 11,
 							messageId: 'asyncUtilWrapper',
 							data: { name: 'myAlias' },
@@ -678,13 +683,14 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -694,7 +700,7 @@ ruleTester.run(RULE_NAME, rule, {
       `,
 					errors: [
 						{
-							line: 13,
+							line: 14,
 							column: 19,
 							messageId: 'asyncUtilWrapper',
 							data: { name: 'waitForAsyncUtil' },
@@ -706,13 +712,14 @@ ruleTester.run(RULE_NAME, rule, {
 			(asyncUtil) =>
 				({
 					code: `
+				// implicitly using ${testingFramework}
         function setup() {
           const utils = render(<MyComponent />);
-        
+
           const waitForAsyncUtil = () => {
             return ${asyncUtil}(screen.queryByTestId('my-test-id'));
           };
-        
+
           return { waitForAsyncUtil, ...utils };
         }
 
@@ -723,7 +730,7 @@ ruleTester.run(RULE_NAME, rule, {
       `,
 					errors: [
 						{
-							line: 14,
+							line: 15,
 							column: 11,
 							messageId: 'asyncUtilWrapper',
 							data: { name: 'myAlias' },
diff --git a/tests/lib/rules/no-dom-import.test.ts b/tests/lib/rules/no-dom-import.test.ts
index e515eb5c..e5fbce0c 100644
--- a/tests/lib/rules/no-dom-import.test.ts
+++ b/tests/lib/rules/no-dom-import.test.ts
@@ -31,22 +31,38 @@ ruleTester.run(RULE_NAME, rule, {
 		'import { foo } from "foo"',
 		'import "foo"',
 		...SUPPORTED_TESTING_FRAMEWORKS.flatMap(({ oldName, newName }) =>
-			[oldName, newName].flatMap((testingFramework) => [
-				`import { fireEvent } from "${testingFramework}"`,
-				`import * as testing from "${testingFramework}"`,
-				`import "${testingFramework}"`,
-			])
+			[oldName, newName !== oldName ? newName : null].flatMap(
+				(testingFramework) => {
+					if (!testingFramework) {
+						return [];
+					}
+
+					return [
+						`import { fireEvent } from "${testingFramework}"`,
+						`import * as testing from "${testingFramework}"`,
+						`import "${testingFramework}"`,
+					];
+				}
+			)
 		),
 		'const { foo } = require("foo")',
 		'require("foo")',
 		'require("")',
 		'require()',
 		...SUPPORTED_TESTING_FRAMEWORKS.flatMap(({ oldName, newName }) =>
-			[oldName, newName].flatMap((testingFramework) => [
-				`const { fireEvent } = require("${testingFramework}")`,
-				`const { fireEvent: testing } = require("${testingFramework}")`,
-				`require("${testingFramework}")`,
-			])
+			[oldName, newName !== oldName ? newName : null].flatMap(
+				(testingFramework) => {
+					if (!testingFramework) {
+						return [];
+					}
+
+					return [
+						`const { fireEvent } = require("${testingFramework}")`,
+						`const { fireEvent: testing } = require("${testingFramework}")`,
+						`require("${testingFramework}")`,
+					];
+				}
+			)
 		),
 		{
 			code: 'import { fireEvent } from "test-utils"',
@@ -61,7 +77,7 @@ ruleTester.run(RULE_NAME, rule, {
 					messageId: 'noDomImport',
 				},
 			],
-			output: 'import { fireEvent } from "dom-testing-library"',
+			output: null,
 		},
 		{
 			settings: {
@@ -76,9 +92,7 @@ ruleTester.run(RULE_NAME, rule, {
 					messageId: 'noDomImport',
 				},
 			],
-			output: `
-      // case: dom-testing-library imported with custom module setting
-      import { fireEvent } from "dom-testing-library"`,
+			output: null,
 		},
 		...SUPPORTED_TESTING_FRAMEWORKS.flatMap(
 			({ configOption, oldName, newName }) =>
diff --git a/tests/lib/rules/no-node-access.test.ts b/tests/lib/rules/no-node-access.test.ts
index f3216e7e..cdecd5a9 100644
--- a/tests/lib/rules/no-node-access.test.ts
+++ b/tests/lib/rules/no-node-access.test.ts
@@ -61,7 +61,7 @@ ruleTester.run(RULE_NAME, rule, {
 			},
 			{
 				code: `
-      // case: code not related to testing library at all
+      // case: code not related to ${testingFramework} at all
       ReactDOM.render(
         <CommProvider useDsa={false}>
           <ThemeProvider>
@@ -116,14 +116,14 @@ ruleTester.run(RULE_NAME, rule, {
 					'testing-library/utils-module': 'test-utils',
 				},
 				code: `
-      // case: custom module set but not imported (aggressive reporting limited)
+      // case: custom module set but not imported using ${testingFramework} (aggressive reporting limited)
       const closestButton = document.getElementById('submit-btn').closest('button');
       expect(closestButton).toBeInTheDocument();
       `,
 			},
 			{
 				code: `
-      // case: without importing TL (aggressive reporting skipped)
+      // case: without importing ${testingFramework} (aggressive reporting skipped)
       const closestButton = document.getElementById('submit-btn')
       expect(closestButton).toBeInTheDocument();
       `,
@@ -141,7 +141,7 @@ ruleTester.run(RULE_NAME, rule, {
 			{
 				// Example from discussions in issue #386
 				code: `
-				import { render } from '@testing-library/react';
+				import { render } from '${testingFramework}';
 				
 				function Wrapper({ children }) {
 					// this should NOT be reported
@@ -165,7 +165,7 @@ ruleTester.run(RULE_NAME, rule, {
 				'testing-library/utils-module': 'test-utils',
 			},
 			code: `
-      // case: importing from custom module (aggressive reporting limited)
+      // case: importing from custom module for ${testingFramework} (aggressive reporting limited)
       import 'test-utils';
       const closestButton = document.getElementById('submit-btn')
       expect(closestButton).toBeInTheDocument();
diff --git a/tests/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts
index 3bf6bed7..2f27043e 100644
--- a/tests/lib/rules/prefer-find-by.test.ts
+++ b/tests/lib/rules/prefer-find-by.test.ts
@@ -40,7 +40,7 @@ ruleTester.run(RULE_NAME, rule, {
 		...ASYNC_QUERIES_COMBINATIONS.map((queryMethod) => ({
 			code: `
         it('tests', async () => {
-          const { ${queryMethod} } = setup()
+          const { ${queryMethod} } = setup() // implicitly using ${testingFramework}
           const submitButton = await ${queryMethod}('foo')
         })
       `,
@@ -251,7 +251,7 @@ ruleTester.run(RULE_NAME, rule, {
 		},
 		// invalid code, as we need findBy* to be defined somewhere, but required for getting 100% coverage
 		{
-			code: `const submitButton = await waitFor(() => getByText('baz', { name: 'button' }))`,
+			code: `const submitButton = await waitFor(() => getByText('baz', { name: 'button' })) // implicitly using ${testingFramework}`,
 			errors: [
 				{
 					messageId: 'preferFindBy',
@@ -263,12 +263,12 @@ ruleTester.run(RULE_NAME, rule, {
 					},
 				},
 			],
-			output: `const submitButton = await findByText('baz', { name: 'button' })`,
+			output: `const submitButton = await findByText('baz', { name: 'button' }) // implicitly using ${testingFramework}`,
 		},
 		// this code would be invalid too, as findByRole is not defined anywhere.
 		{
 			code: `
-          const getByRole = render().getByRole
+          const getByRole = render().getByRole // implicitly using ${testingFramework}
           const submitButton = await waitFor(() => getByRole('baz', { name: 'button' }))
         `,
 			errors: [
@@ -283,7 +283,7 @@ ruleTester.run(RULE_NAME, rule, {
 				},
 			],
 			output: `
-          const getByRole = render().getByRole
+          const getByRole = render().getByRole // implicitly using ${testingFramework}
           const submitButton = await findByRole('baz', { name: 'button' })
         `,
 		},
@@ -306,13 +306,7 @@ ruleTester.run(RULE_NAME, rule, {
 					},
 				},
 			],
-			output: `
-          import { waitFor, render} from '${testingFramework}';
-          it('tests', async () => {
-            const { getByCustomQuery } = render()
-            const submitButton = await waitFor(() => getByCustomQuery('baz'))
-          })
-        `,
+			output: null,
 		},
 		// custom query triggers the error but there is no fix - so output is the same
 		{
@@ -333,13 +327,7 @@ ruleTester.run(RULE_NAME, rule, {
 					},
 				},
 			],
-			output: `
-          import {waitFor,render,screen} from '${testingFramework}';
-          it('tests', async () => {
-            const { getByCustomQuery } = render()
-            const submitButton = await waitFor(() => screen.getByCustomQuery('baz'))
-          })
-        `,
+			output: null,
 		},
 		// presence matchers
 		...createScenario((waitMethod: string, queryMethod: string) => ({
diff --git a/tests/lib/rules/prefer-query-matchers.test.ts b/tests/lib/rules/prefer-query-matchers.test.ts
index e16cf983..908f1b27 100644
--- a/tests/lib/rules/prefer-query-matchers.test.ts
+++ b/tests/lib/rules/prefer-query-matchers.test.ts
@@ -24,6 +24,7 @@ type AssertionFnParams = {
 	query: string;
 	matcher: string;
 	options: Options;
+	onlyOptions?: boolean;
 };
 
 const wrapExpectInTest = (expectStatement: string) => `
@@ -39,28 +40,37 @@ const getValidAssertions = ({
 	query,
 	matcher,
 	options,
+	onlyOptions = false,
 }: AssertionFnParams): RuleValidTestCase[] => {
 	const expectStatement = `expect(${query}('Hello'))${matcher}`;
 	const expectScreenStatement = `expect(screen.${query}('Hello'))${matcher}`;
-	return [
-		{
-			// name: `${expectStatement} with default options of empty validEntries`,
-			code: wrapExpectInTest(expectStatement),
-		},
+	const casesWithOptions = [
 		{
 			// name: `${expectStatement} with provided options`,
 			code: wrapExpectInTest(expectStatement),
 			options,
 		},
 		{
-			// name: `${expectScreenStatement} with default options of empty validEntries`,
+			// name: `${expectScreenStatement} with provided options`,
 			code: wrapExpectInTest(expectScreenStatement),
+			options,
 		},
+	];
+
+	if (onlyOptions) {
+		return casesWithOptions;
+	}
+
+	return [
 		{
-			// name: `${expectScreenStatement} with provided options`,
+			// name: `${expectStatement} with default options of empty validEntries`,
+			code: wrapExpectInTest(expectStatement),
+		},
+		{
+			// name: `${expectScreenStatement} with default options of empty validEntries`,
 			code: wrapExpectInTest(expectScreenStatement),
-			options,
 		},
+		...casesWithOptions,
 	];
 };
 
@@ -150,6 +160,7 @@ ruleTester.run(RULE_NAME, rule, {
 					options: [
 						{ validEntries: [{ query: 'query', matcher: 'toBeVisible' }] },
 					],
+					onlyOptions: true,
 				}),
 			],
 			[]
@@ -192,6 +203,7 @@ ruleTester.run(RULE_NAME, rule, {
 					options: [
 						{ validEntries: [{ query: 'query', matcher: 'toBeVisible' }] },
 					],
+					onlyOptions: true,
 				}),
 			],
 			[]
@@ -234,6 +246,7 @@ ruleTester.run(RULE_NAME, rule, {
 					options: [
 						{ validEntries: [{ query: 'get', matcher: 'toBeVisible' }] },
 					],
+					onlyOptions: true,
 				}),
 			],
 			[]
@@ -276,6 +289,7 @@ ruleTester.run(RULE_NAME, rule, {
 					options: [
 						{ validEntries: [{ query: 'get', matcher: 'toBeVisible' }] },
 					],
+					onlyOptions: true,
 				}),
 			],
 			[]
diff --git a/tests/lib/test-utils.ts b/tests/lib/test-utils.ts
index c9640129..aed679c7 100644
--- a/tests/lib/test-utils.ts
+++ b/tests/lib/test-utils.ts
@@ -2,11 +2,13 @@ import { resolve } from 'path';
 
 import { TSESLint } from '@typescript-eslint/utils';
 
+import { FlatCompatRuleTester } from './FlatCompatRuleTester';
+
 const DEFAULT_TEST_CASE_CONFIG = {
 	filename: 'MyComponent.test.js',
 };
 
-class TestingLibraryRuleTester extends TSESLint.RuleTester {
+class TestingLibraryRuleTester extends FlatCompatRuleTester {
 	run<TMessageIds extends string, TOptions extends Readonly<unknown[]>>(
 		ruleName: string,
 		rule: TSESLint.RuleModule<TMessageIds, TOptions>,