diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-29 00:37:56 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-05-02 03:27:23 +0200 |
| commit | a9b9cf6bf50f08723896659634f2e6afc17bcd42 (patch) | |
| tree | 9175b49ff21f4326872ac9e541d2765cc7994ebc /eslint/rules/__tests__/prefer-object-params.test.mjs | |
| parent | d6622dc6b3523a5658cd512b44f15f7a66fd540c (diff) | |
| download | opencut-a9b9cf6bf50f08723896659634f2e6afc17bcd42.tar.gz | |
chore: switch from biome to eslint + prettier; fix ton of lint issues
Diffstat (limited to 'eslint/rules/__tests__/prefer-object-params.test.mjs')
| -rw-r--r-- | eslint/rules/__tests__/prefer-object-params.test.mjs | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/eslint/rules/__tests__/prefer-object-params.test.mjs b/eslint/rules/__tests__/prefer-object-params.test.mjs new file mode 100644 index 00000000..7f4bc947 --- /dev/null +++ b/eslint/rules/__tests__/prefer-object-params.test.mjs @@ -0,0 +1,112 @@ +import { afterAll, describe, it } from "bun:test"; +import { RuleTester } from "eslint"; +import tseslint from "typescript-eslint"; +import rule from "../prefer-object-params.mjs"; + +RuleTester.describe = describe; +RuleTester.it = it; +RuleTester.itOnly = it.only; +RuleTester.afterAll = afterAll; + +const ruleTester = new RuleTester({ + languageOptions: { + parser: tseslint.parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, + }, + }, +}); + +const error = { messageId: "preferObjectParams" }; + +ruleTester.run("prefer-object-params", rule, { + valid: [ + { code: "function f() {}" }, + { code: "function f(a) {}" }, + { code: "const f = (a) => a;" }, + { code: "const f = function (a) { return a; };" }, + { code: "function f({ a, b }) { return a + b; }" }, + { code: "function f({ a, b }: { a: number; b: number }) { return a + b; }" }, + + // Direct callbacks: positional args are dictated by the caller. + { code: "[1, 2, 3].reduce((acc, cur) => acc + cur, 0);" }, + { code: "arr.map((value, index) => value + index);" }, + { code: "new Promise((resolve, reject) => {});" }, + { code: "maybeFn?.((a, b) => a + b);" }, + + // Inline option-object callbacks: caller dictates the signature + // (e.g. zustand's `persist`, tanstack-query's `onSuccess`). + { code: "someAPI({ on: (a, b) => a + b });" }, + { + code: "persist((set, get) => ({}), { migrate: (state, version) => state });", + }, + { code: "new Foo({ handler: (a, b) => a + b });" }, + { code: "maybeFn?.({ handler: (a, b) => a + b });" }, + + // Deeply-nested inline option callbacks still qualify: the entire + // option tree is supplied at the call site, so the caller dictates + // every signature inside it. Real example: better-auth's + // `betterAuth({ rateLimit: { customStorage: { set: (k, v) => ... }}})`. + { code: "someAPI({ outer: { handler: (a, b) => a + b } });" }, + + // Type predicates require a positional subject. + { + code: "function isFoo(x: unknown, ctx: Ctx): x is Foo { return true; }", + }, + { + code: "const isFoo = (x: unknown, ctx: Ctx): x is Foo => true;", + }, + ], + invalid: [ + { + code: "function f(a, b) { return a + b; }", + errors: [error], + }, + { + code: "const f = (a, b) => a + b;", + errors: [error], + }, + { + code: "const f = function (a, b) { return a + b; };", + errors: [error], + }, + { + code: "function f(a, b, c) { return a + b + c; }", + errors: [error], + }, + { + code: "function formatTime(seconds: number, fps: number) { return seconds * fps; }", + errors: [error], + }, + // A callback assigned to a name and passed indirectly is still a + // signature the author controls; the carve-out is intentionally narrow + // to direct callbacks. + { + code: "const cb = (a, b) => a + b; arr.reduce(cb, 0);", + errors: [error], + }, + // Pre-declared option object: once the object is named, its callback + // signatures are the author's to evolve. + { + code: "const opts = { handler: (a, b) => a + b }; someAPI(opts);", + errors: [error], + }, + // The error must point at the function's declaration line, so a + // `// eslint-disable-next-line` placed above the declaration (the + // conventional spot) suppresses the violation even when the + // signature spans multiple lines. Reporting at a parameter row + // instead leaves the directive inert and `reportUnusedDisableDirectives` + // then flags every suppressed call site. + { + code: [ + "function f(", + "\ta: number,", + "\tb: number,", + ") { return a + b; }", + ].join("\n"), + errors: [{ messageId: "preferObjectParams", line: 1 }], + }, + ], +}); |
