@gaia-harness/lint 3.0.4 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -4
- package/dist/configs/guardrails.d.ts.map +1 -1
- package/dist/configs/react-doctor.d.ts +70 -0
- package/dist/configs/react-doctor.d.ts.map +1 -0
- package/dist/configs/react-router.d.ts +4 -1
- package/dist/configs/react-router.d.ts.map +1 -1
- package/dist/configs/react.d.ts.map +1 -1
- package/dist/configs/shadcn.d.ts.map +1 -1
- package/dist/configs/style-hygiene.d.ts.map +1 -1
- package/dist/index.js +330 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -67,17 +67,17 @@ new source root: no per-config override blocks needed.
|
|
|
67
67
|
| Property | Shape | Includes | Required? |
|
|
68
68
|
| ---------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
|
|
69
69
|
| `base` | `Linter.Config[]` | JS recommended, TypeScript (typescript-eslint), `import-x`, unused-disable-directive reporting at error, `eslint-comments` (every disable states a reason), `prefer-arrow-functions`, lodash/underscore guard, `no-restricted-imports` (bans bare `@conform-to/zod`) | required |
|
|
70
|
-
| `react` | `Linter.Config[]` | `eslint-plugin-react`, `react-hooks`, `jsx-a11y`, `react-refresh` (Fast Refresh-safe exports), `react-you-might-not-need-an-effect` (Effect misuse), nine `@eslint-react` rules (Effect cleanup, React 19 forms), GAIA-specific React rules | required for React apps |
|
|
71
|
-
| `reactRouter` | `Linter.Config[]` | Relaxations for React Router framework mode, route-module exports allowed by `react-refresh`, plus `no-imperative-query-fetch` (custom). Spread **after** `react`. See [Router-specific rules](#router-specific-rules) | React Router only |
|
|
70
|
+
| `react` | `Linter.Config[]` | `eslint-plugin-react`, `react-hooks`, `jsx-a11y`, `react-refresh` (Fast Refresh-safe exports), `react-you-might-not-need-an-effect` (Effect misuse), nine `@eslint-react` rules (Effect cleanup, React 19 forms), 77 curated `react-doctor` rules (SSR and hydration, HTML validity, accessibility, render and Effect correctness, performance, motion, component size and complexity), GAIA-specific React rules. See [react-doctor rules](#react-doctor-rules) | required for React apps |
|
|
71
|
+
| `reactRouter` | `Linter.Config[]` | Relaxations for React Router framework mode, route-module exports allowed by `react-refresh`, plus a TanStack Query block (`no-imperative-query-fetch` (custom), `@tanstack/eslint-plugin-query` recommended, five `react-doctor` Query rules) and 25 curated `react-doctor` router rules with the capability settings they need. Spread **after** `react`. See [Router-specific rules](#router-specific-rules) | React Router only |
|
|
72
72
|
| `styleHygiene` | `Linter.Config[]` | `canonical`, `perfectionist`, `unicorn`, `unused-imports`, `check-file` | required |
|
|
73
|
-
| `guardrails` | `Linter.Config[]` | `regexp` (recommended, ReDoS included), `no-enum` (custom), `no-switch` (custom), `no-jsx-iife` (custom), `no-null-render` (custom), `no-zod-enum` (custom), `cn-conditional` (custom), `no-relative-import-paths`, `sonarjs`, `import-x`, `prefer-arrow-functions` | required |
|
|
73
|
+
| `guardrails` | `Linter.Config[]` | `regexp` (recommended, ReDoS included), `no-enum` (custom), `no-switch` (custom), `no-jsx-iife` (custom), `no-null-render` (custom), `no-zod-enum` (custom), `cn-conditional` (custom), `no-relative-import-paths`, `sonarjs`, `import-x`, `prefer-arrow-functions`, 14 curated `react-doctor` security and defensive-JS rules | required |
|
|
74
74
|
| `i18n` | `Linter.Config[]` | `i18next/no-literal-string` on raw JSX text under the source dir. See [i18n and Zod](#i18n-and-zod) | optional |
|
|
75
75
|
| `zod` | `Linter.Config[]` | `eslint-plugin-zod` recommended, minus `consistent-import`. See [i18n and Zod](#i18n-and-zod) | optional |
|
|
76
76
|
| `testing` | `Linter.Config[]` | Vitest + jest-dom config scoped to `*.test.*`, `*.stories.*` and `test/` | optional |
|
|
77
77
|
| `storybook` | `Linter.Config[]` | `eslint-plugin-storybook` scoped to `*.stories.*` and `.storybook/main.*` | optional |
|
|
78
78
|
| `playwright` | `Linter.Config[]` | `eslint-plugin-playwright` scoped to `.playwright/` | optional |
|
|
79
79
|
| `prettier` | `Linter.Config[]` | `eslint-config-prettier`, must be **last** to disable formatting rules | required if using Prettier |
|
|
80
|
-
| `shadcn` | `(opts?) => Linter.Config[]` | `@shadcn/lint` token rules plus the vendored-ui exemption; see [shadcn factory](#shadcn-factory). Spread **last**, after `prettier`, with the full GAIA composition before it | optional |
|
|
80
|
+
| `shadcn` | `(opts?) => Linter.Config[]` | `@shadcn/lint` token rules, four curated `react-doctor` composition rules, plus the vendored-ui exemption; see [shadcn factory](#shadcn-factory). Spread **last**, after `prettier`, with the full GAIA composition before it | optional |
|
|
81
81
|
| `betterTailwind` | `(opts) => Linter.Config[]` | `eslint-plugin-better-tailwindcss` factory; takes `entryPoint` (path to Tailwind entry CSS) and optional `ignore` (class names to skip) | optional |
|
|
82
82
|
| `ignores` | `Iterable<Linter.Config> & ((opts?) => Linter.Config[])` | `includeIgnoreFile` helper plus GAIA defaults. Spread directly for defaults (`...lint.ignores`) or call with options to override (`...lint.ignores({extra: ['.gaia/**']})`). | recommended |
|
|
83
83
|
|
|
@@ -287,6 +287,29 @@ The `/.react-router/**` glob stays in the `ignores` defaults. It names a
|
|
|
287
287
|
directory that only exists in a React Router project, so it costs other
|
|
288
288
|
projects nothing.
|
|
289
289
|
|
|
290
|
+
## react-doctor rules
|
|
291
|
+
|
|
292
|
+
125 rules from [`eslint-plugin-react-doctor`](https://www.react.doctor/docs/configuration/eslint-and-oxlint-plugins), chosen one by one; no preset is spread. The presets turn back on rules this config turns off on purpose and carry hundreds of rules for libraries it does not cover. The plugin is pinned to an exact version because its rule set changes between releases, and ESLint fails a config that names a rule the installed plugin no longer ships. Every curated id and severity lives in `src/configs/react-doctor.ts`, grouped with the reason for each group, and a snapshot test pins the full list.
|
|
293
|
+
|
|
294
|
+
| Block | Groups |
|
|
295
|
+
| --- | --- |
|
|
296
|
+
| `react` (77) | SSR and hydration, HTML validity, accessibility, render and state purity, Effect lifecycle, performance (including images and scripts), motion and font, component and hook size and complexity |
|
|
297
|
+
| `reactRouter` (30) | 24 framework-mode rules (loaders, actions, middleware, sessions, cookies, search params, navigation, error UI, the server entry); five Query rules in the Query block; `no-unguarded-browser-global-at-module-scope` on source files only, not tests or stories |
|
|
298
|
+
| `guardrails` (14) | auth tokens in web storage, secrets in client code, dynamic `RegExp` input, path containment, `window.open` without `noopener`, throwing and NaN-producing parses, and similar |
|
|
299
|
+
| `shadcn()` (4) | icon Button label, DialogContent title, InputGroup controls, TabsTrigger inside TabsList |
|
|
300
|
+
|
|
301
|
+
The vendored shadcn ui files skip the four shadcn rules (they check how GAIA code composes the primitives) and the three motion and font rules (registry classes).
|
|
302
|
+
|
|
303
|
+
`no-giant-component` reports a component over 300 lines. `no-high-complexity-react-function` reports a component or `use*` hook over 15 cyclomatic or cognitive complexity. Both thresholds are fixed upstream. They stand in for the sonarjs size and complexity rules, which stay off because they do not model components and hooks.
|
|
304
|
+
|
|
305
|
+
`reactRouter` also sets `settings['react-doctor'].capabilities`. The react-doctor CLI detects these per project; the standalone plugin reads them from settings, and without `react-router-framework` the router rules never recognize a route module's named exports. Once the list is set, a rule runs only when every capability it requires is listed verbatim, so an override that adds a react-doctor rule may need to extend the list.
|
|
306
|
+
|
|
307
|
+
### TanStack Query
|
|
308
|
+
|
|
309
|
+
`reactRouter`'s Query block also enables `@tanstack/eslint-plugin-query`'s recommended rules, read from its flat preset, beside `no-imperative-query-fetch`. They match TanStack Query imports, so a project without Query sees nothing. react-doctor's `query-no-rest-destructuring`, `query-stable-query-client` and `query-no-void-query-fn` stay off because the official plugin owns them. TanStack's property-order rules need `onMutate` before `onError` and `queryFn` before the page-param getters, which alphabetical order breaks. So in the object passed straight to those calls, `perfectionist/sort-objects` keeps natural order but sorts those late-binding keys into a last group: `onError` and `onSettled` for `useMutation`; `getNextPageParam` and `getPreviousPageParam` for `useInfiniteQuery`, `useSuspenseInfiniteQuery` and `infiniteQueryOptions`. Its autofix lands on the order TanStack requires. `no-void-query-fn` needs type information.
|
|
310
|
+
|
|
311
|
+
`react` also turns `react/jsx-key` back on (error, with `checkFragmentShorthand` and `warnOnDuplicates`): Airbnb's non-strict React config turns it off, and nothing else here catches a list rendered without `key`.
|
|
312
|
+
|
|
290
313
|
## i18n and Zod
|
|
291
314
|
|
|
292
315
|
Both are opt-in arrays: spread them only when the project uses the library.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/configs/guardrails.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../../src/configs/guardrails.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,QAAQ,CAAC;AA0SnC,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,KAAG,MAAM,CAAC,MAAM,EAYhE,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ESLint, Linter } from 'eslint';
|
|
2
|
+
/**
|
|
3
|
+
* The one `eslint-plugin-react-doctor` registration every block that names a
|
|
4
|
+
* `react-doctor/*` rule spreads into its `plugins`. Flat config accepts the
|
|
5
|
+
* same plugin object in several blocks, so `react`, `reactRouter`, `shadcn()`
|
|
6
|
+
* and `guardrails` each stay self-contained without a redefinition error.
|
|
7
|
+
*
|
|
8
|
+
* Curated rules only, never a preset: the presets re-enable rules GAIA turns
|
|
9
|
+
* off on purpose and carry hundreds of rules for libraries GAIA does not use.
|
|
10
|
+
* The rules take no options, carry no autofix and need no type information,
|
|
11
|
+
* so the same rule ids port unchanged to Oxlint's `oxlint-plugin-react-doctor`.
|
|
12
|
+
*
|
|
13
|
+
* The package is pinned to an exact version. The rule set churns between
|
|
14
|
+
* releases, and ESLint fails a config that names a rule the installed plugin
|
|
15
|
+
* no longer ships, so a bump is a deliberate change with its tests. Every
|
|
16
|
+
* curated rule id lives in this file, grouped by the block that applies it,
|
|
17
|
+
* so a bump is checked against one list.
|
|
18
|
+
*/
|
|
19
|
+
export declare const REACT_DOCTOR_PLUGINS: Record<string, ESLint.Plugin>;
|
|
20
|
+
/**
|
|
21
|
+
* `react`: motion that ignores reduced-motion and pixel font sizes that ignore
|
|
22
|
+
* user font scaling. Off in vendored shadcn ui (see `shadcn.ts`).
|
|
23
|
+
*/
|
|
24
|
+
export declare const MOTION_FONT_RULES: Linter.RulesRecord;
|
|
25
|
+
export declare const REACT_DOCTOR_REACT_RULES: Linter.RulesRecord;
|
|
26
|
+
/**
|
|
27
|
+
* `reactRouter`: framework-mode mistakes in loaders, actions, middleware,
|
|
28
|
+
* sessions, cookies, search params, navigation and the server entry.
|
|
29
|
+
*/
|
|
30
|
+
export declare const REACT_DOCTOR_REACT_ROUTER_RULES: Linter.RulesRecord;
|
|
31
|
+
/**
|
|
32
|
+
* `reactRouter`, in the Query block: TanStack Query misuse the official
|
|
33
|
+
* plugin does not check. Its `no-rest-destructuring`, `stable-query-client`
|
|
34
|
+
* and `no-void-query-fn` own those cases, so react-doctor's copies stay off.
|
|
35
|
+
*/
|
|
36
|
+
export declare const REACT_DOCTOR_QUERY_RULES: Linter.RulesRecord;
|
|
37
|
+
/** `reactRouter`: source modules only; a module-scope browser global crashes SSR. */
|
|
38
|
+
export declare const REACT_DOCTOR_MODULE_SCOPE_RULES: Linter.RulesRecord;
|
|
39
|
+
/** `guardrails`: security and defensive-JS classes sonarjs and regexp miss. */
|
|
40
|
+
export declare const REACT_DOCTOR_GUARDRAIL_RULES: Linter.RulesRecord;
|
|
41
|
+
/**
|
|
42
|
+
* `shadcn()`: composition that breaks a shadcn primitive's accessible name,
|
|
43
|
+
* keyboard model or structure. Off in vendored ui, which defines the
|
|
44
|
+
* primitives these check.
|
|
45
|
+
*/
|
|
46
|
+
export declare const REACT_DOCTOR_SHADCN_RULES: Linter.RulesRecord;
|
|
47
|
+
/**
|
|
48
|
+
* The capabilities `reactRouter` declares in `settings['react-doctor']`.
|
|
49
|
+
* react-doctor's CLI detects them per project; the standalone plugin reads
|
|
50
|
+
* them from settings. Without `react-router-framework` the react-router rules
|
|
51
|
+
* never recognize a route module's named `loader`, `action` or
|
|
52
|
+
* `ErrorBoundary` exports and report nothing.
|
|
53
|
+
*
|
|
54
|
+
* Once the list is set, a rule runs only when every entry in its `requires`
|
|
55
|
+
* appears here verbatim. A versioned entry such as `react-router:7.9` is a
|
|
56
|
+
* minimum-version claim ("at least 7.9"), matched as a literal string, never
|
|
57
|
+
* as a range: a project on React Router 8 must still list each lower floor a
|
|
58
|
+
* rule names. So the list carries every versioned entry the plugin uses that
|
|
59
|
+
* GAIA's stack floors satisfy (React Router 8, React 19.3, Tailwind 4, Zod 4),
|
|
60
|
+
* whether or not a curated rule needs it today, so a rule added later finds
|
|
61
|
+
* its floor. A rule whose `requires` is missing from this list stays silent
|
|
62
|
+
* with no error; a test in `shadcn.test.ts` reads each enabled rule's
|
|
63
|
+
* `requires` from the plugin's metadata and fails on any gap.
|
|
64
|
+
*
|
|
65
|
+
* `shadcn`, `base-ui` and `tanstack-query` are listed too: their rules match
|
|
66
|
+
* the components and hooks they check by import, so they stay silent in a
|
|
67
|
+
* project without that library.
|
|
68
|
+
*/
|
|
69
|
+
export declare const REACT_DOCTOR_CAPABILITIES: string[];
|
|
70
|
+
//# sourceMappingURL=react-doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-doctor.d.ts","sourceRoot":"","sources":["../../src/configs/react-doctor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,MAAM,EAAE,MAAM,EAAC,MAAM,QAAQ,CAAC;AAE3C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAE9D,CAAC;AA+GF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,WAItC,CAAC;AAaF,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,WAS7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,WAyBpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,WAM7C,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,WAEpD,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC,WAejD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,WAK9C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,yBAAyB,UAwBrC,CAAC"}
|
|
@@ -21,7 +21,10 @@ import type { Linter } from 'eslint';
|
|
|
21
21
|
* (`createFileRoute(...)({component: Named})`) and never produces that shape,
|
|
22
22
|
* so a TanStack project wants the rule left on.
|
|
23
23
|
*
|
|
24
|
-
* It also carries
|
|
24
|
+
* It also carries ADDITIVE rules on every TypeScript file under the source
|
|
25
|
+
* dir: the curated react-doctor React Router and Query rules with the
|
|
26
|
+
* capability settings they need, the `@tanstack/eslint-plugin-query`
|
|
27
|
+
* recommended rules, and `no-imperative-query-fetch`. That last one sits on every
|
|
25
28
|
* TypeScript file under the source dir. It bans `ensureQueryData`,
|
|
26
29
|
* `fetchQuery`, `prefetchQuery` and their infinite variants, so a route has
|
|
27
30
|
* one fetch idiom: `queryClient.query` awaited in a clientLoader, read with
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../../src/configs/react-router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../../src/configs/react-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAS,MAAM,EAAC,MAAM,QAAQ,CAAC;AA4C3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,KAAG,MAAM,CAAC,MAAM,EAsEjE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/configs/react.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAS,MAAM,EAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/configs/react.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAS,MAAM,EAAC,MAAM,QAAQ,CAAC;AAM3C;;;;;;;;;GASG;AACH;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B;;;CAGjC,CAAC;AAEX,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAgIhC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shadcn.d.ts","sourceRoot":"","sources":["../../src/configs/shadcn.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAS,MAAM,EAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"shadcn.d.ts","sourceRoot":"","sources":["../../src/configs/shadcn.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAS,MAAM,EAAC,MAAM,QAAQ,CAAC;AAQ3C,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAgMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,WAAW,GACtB,WAAW,MAAM,EACjB,OAAO,qBAAqB,KAC3B,MAAM,CAAC,MAAM,EAsEf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-hygiene.d.ts","sourceRoot":"","sources":["../../src/configs/style-hygiene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"style-hygiene.d.ts","sourceRoot":"","sources":["../../src/configs/style-hygiene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,QAAQ,CAAC;AAyVnC,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,KAAG,MAAM,CAAC,MAAM,EAMlE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -703,6 +703,198 @@ var plugin6 = {
|
|
|
703
703
|
};
|
|
704
704
|
var no_zod_enum_default = plugin6;
|
|
705
705
|
|
|
706
|
+
// src/configs/react-doctor.ts
|
|
707
|
+
import reactDoctor from "eslint-plugin-react-doctor";
|
|
708
|
+
var REACT_DOCTOR_PLUGINS = {
|
|
709
|
+
"react-doctor": reactDoctor
|
|
710
|
+
};
|
|
711
|
+
var SSR_RULES = {
|
|
712
|
+
"react-doctor/no-hydration-branch-on-browser-global": "error",
|
|
713
|
+
"react-doctor/no-locale-format-in-render": "warn",
|
|
714
|
+
"react-doctor/no-match-media-in-state-initializer": "warn",
|
|
715
|
+
"react-doctor/no-unguarded-browser-global-in-render-or-hook-init": "error"
|
|
716
|
+
};
|
|
717
|
+
var HTML_RULES = {
|
|
718
|
+
"react-doctor/form-control-requires-name": "warn",
|
|
719
|
+
"react-doctor/html-no-invalid-paragraph-child": "warn",
|
|
720
|
+
"react-doctor/html-no-invalid-table-nesting": "warn",
|
|
721
|
+
"react-doctor/html-no-nested-form": "warn",
|
|
722
|
+
"react-doctor/html-no-nested-interactive": "warn",
|
|
723
|
+
"react-doctor/no-inert-sticky-position": "warn"
|
|
724
|
+
};
|
|
725
|
+
var A11Y_RULES = {
|
|
726
|
+
"react-doctor/data-table-requires-accessible-name": "warn",
|
|
727
|
+
"react-doctor/details-requires-summary": "warn",
|
|
728
|
+
"react-doctor/dialog-has-accessible-name": "warn",
|
|
729
|
+
"react-doctor/empty-table-header": "warn",
|
|
730
|
+
"react-doctor/fieldset-requires-legend": "warn",
|
|
731
|
+
"react-doctor/html-label-has-single-control": "warn",
|
|
732
|
+
"react-doctor/no-aria-invalid-without-description": "warn",
|
|
733
|
+
"react-doctor/no-assertive-status": "warn",
|
|
734
|
+
"react-doctor/no-autoplay-without-muted": "warn",
|
|
735
|
+
"react-doctor/no-blocked-paste": "error",
|
|
736
|
+
"react-doctor/no-disabled-zoom": "error",
|
|
737
|
+
"react-doctor/no-duplicate-static-id-reference": "error",
|
|
738
|
+
"react-doctor/no-focusable-content-in-aria-hidden": "warn",
|
|
739
|
+
"react-doctor/no-hover-only-reveal": "warn",
|
|
740
|
+
"react-doctor/no-invalid-progress-range": "error",
|
|
741
|
+
"react-doctor/no-multiple-main-landmarks": "warn",
|
|
742
|
+
"react-doctor/no-multiple-unlabeled-navigation-landmarks": "warn",
|
|
743
|
+
"react-doctor/no-placeholder-only-field": "warn",
|
|
744
|
+
"react-doctor/no-presentation-role-conflict": "warn",
|
|
745
|
+
"react-doctor/no-responsive-hidden-accessible-name": "warn",
|
|
746
|
+
"react-doctor/no-skipped-heading-level": "warn",
|
|
747
|
+
"react-doctor/no-uninformative-aria-label": "warn",
|
|
748
|
+
"react-doctor/radio-input-missing-name": "warn",
|
|
749
|
+
"react-doctor/role-button-requires-complete-keyboard-activation": "warn"
|
|
750
|
+
};
|
|
751
|
+
var RENDER_STATE_RULES = {
|
|
752
|
+
"react-doctor/no-call-component-as-function": "warn",
|
|
753
|
+
"react-doctor/no-controlled-input-value-without-state-update": "warn",
|
|
754
|
+
"react-doctor/no-create-context-in-render": "error",
|
|
755
|
+
"react-doctor/no-create-ref-in-function-component": "warn",
|
|
756
|
+
"react-doctor/no-default-props": "warn",
|
|
757
|
+
"react-doctor/no-impure-call-at-module-scope": "warn",
|
|
758
|
+
"react-doctor/no-indeterminate-attribute": "warn",
|
|
759
|
+
"react-doctor/no-mutate-queried-dom-node-in-component": "warn",
|
|
760
|
+
"react-doctor/no-mutating-reducer-state": "error",
|
|
761
|
+
"react-doctor/no-nondeterministic-id-value-in-render-body": "warn",
|
|
762
|
+
"react-doctor/no-prop-callback-in-render": "error",
|
|
763
|
+
"react-doctor/no-side-effect-in-state-updater-function": "warn",
|
|
764
|
+
"react-doctor/no-uncontrolled-input": "warn",
|
|
765
|
+
"react-doctor/rerender-functional-setstate": "warn",
|
|
766
|
+
"react-doctor/waapi-animation-in-render": "error"
|
|
767
|
+
};
|
|
768
|
+
var EFFECT_RULES = {
|
|
769
|
+
"react-doctor/debounce-no-cleanup": "warn",
|
|
770
|
+
"react-doctor/effect-raf-loop-needs-cancel": "warn",
|
|
771
|
+
"react-doctor/no-create-object-url-without-revoke": "warn",
|
|
772
|
+
"react-doctor/no-fetch-in-effect": "warn",
|
|
773
|
+
"react-doctor/no-fetch-response-used-without-status-check": "warn",
|
|
774
|
+
"react-doctor/no-loading-flag-reset-outside-finally": "warn",
|
|
775
|
+
"react-doctor/no-promise-then-side-effect-in-effect-without-catch": "warn",
|
|
776
|
+
"react-doctor/no-set-state-after-await-in-effect": "warn",
|
|
777
|
+
"react-doctor/no-stale-timer-ref": "warn",
|
|
778
|
+
"react-doctor/no-unbounded-animation-frame-loop": "warn",
|
|
779
|
+
"react-doctor/no-unowned-async-error-clear": "warn",
|
|
780
|
+
"react-doctor/pointer-capture-needs-cancel-handler": "warn"
|
|
781
|
+
};
|
|
782
|
+
var PERFORMANCE_RULES = {
|
|
783
|
+
"react-doctor/async-parallel": "warn",
|
|
784
|
+
"react-doctor/js-hoist-intl": "warn",
|
|
785
|
+
"react-doctor/no-eager-new-in-use-state-initializer": "warn",
|
|
786
|
+
"react-doctor/no-img-lazy-with-high-fetchpriority": "warn",
|
|
787
|
+
"react-doctor/no-img-without-dimensions": "warn",
|
|
788
|
+
"react-doctor/no-mixed-srcset-descriptors": "warn",
|
|
789
|
+
"react-doctor/no-spread-accumulator-in-reduce": "warn",
|
|
790
|
+
"react-doctor/no-srcset-without-sizes": "warn",
|
|
791
|
+
"react-doctor/rendering-script-defer-async": "warn",
|
|
792
|
+
"react-doctor/rerender-lazy-ref-init": "warn",
|
|
793
|
+
"react-doctor/rerender-lazy-state-init": "warn"
|
|
794
|
+
};
|
|
795
|
+
var MOTION_FONT_RULES = {
|
|
796
|
+
"react-doctor/no-arbitrary-px-font-size": "warn",
|
|
797
|
+
"react-doctor/no-smooth-scroll-without-reduced-motion": "warn",
|
|
798
|
+
"react-doctor/no-ungated-tailwind-animation": "warn"
|
|
799
|
+
};
|
|
800
|
+
var SIZE_RULES = {
|
|
801
|
+
"react-doctor/no-giant-component": "warn",
|
|
802
|
+
"react-doctor/no-high-complexity-react-function": "warn"
|
|
803
|
+
};
|
|
804
|
+
var REACT_DOCTOR_REACT_RULES = {
|
|
805
|
+
...SSR_RULES,
|
|
806
|
+
...HTML_RULES,
|
|
807
|
+
...A11Y_RULES,
|
|
808
|
+
...RENDER_STATE_RULES,
|
|
809
|
+
...EFFECT_RULES,
|
|
810
|
+
...PERFORMANCE_RULES,
|
|
811
|
+
...MOTION_FONT_RULES,
|
|
812
|
+
...SIZE_RULES
|
|
813
|
+
};
|
|
814
|
+
var REACT_DOCTOR_REACT_ROUTER_RULES = {
|
|
815
|
+
"react-doctor/react-router-csp-nonce-consistency": "error",
|
|
816
|
+
"react-doctor/react-router-guard-aborted-handle-error": "warn",
|
|
817
|
+
"react-doctor/react-router-internal-route-anchor": "warn",
|
|
818
|
+
"react-doctor/react-router-loader-fetch-forwards-signal": "warn",
|
|
819
|
+
"react-doctor/react-router-loader-parallel-fetch": "warn",
|
|
820
|
+
"react-doctor/react-router-no-catch-middleware-next": "warn",
|
|
821
|
+
"react-doctor/react-router-no-client-module-in-server-render": "error",
|
|
822
|
+
"react-doctor/react-router-no-loader-request-body": "error",
|
|
823
|
+
"react-doctor/react-router-no-middleware-response-body-consumption": "warn",
|
|
824
|
+
"react-doctor/react-router-no-multiple-blockers": "error",
|
|
825
|
+
"react-doctor/react-router-no-multiple-middleware-next": "error",
|
|
826
|
+
"react-doctor/react-router-no-multiple-set-search-params-in-tick": "warn",
|
|
827
|
+
"react-doctor/react-router-no-navigate-in-render": "error",
|
|
828
|
+
"react-doctor/react-router-no-redirect-in-try-catch": "error",
|
|
829
|
+
"react-doctor/react-router-no-route-module-environment-suffix": "error",
|
|
830
|
+
"react-doctor/react-router-no-session-mutation-in-loader": "error",
|
|
831
|
+
"react-doctor/react-router-no-static-cookie-expires": "error",
|
|
832
|
+
"react-doctor/react-router-no-unsynchronized-search-params-mutation": "error",
|
|
833
|
+
"react-doctor/react-router-no-use-loader-data-in-error-ui": "error",
|
|
834
|
+
"react-doctor/react-router-require-root-error-boundary": "error",
|
|
835
|
+
"react-doctor/react-router-resource-link-requires-reload": "error",
|
|
836
|
+
"react-doctor/react-router-return-navigation-promise-in-transition": "warn",
|
|
837
|
+
"react-doctor/react-router-server-middleware-return-response": "error",
|
|
838
|
+
"react-doctor/react-router-session-mutation-requires-commit": "error"
|
|
839
|
+
};
|
|
840
|
+
var REACT_DOCTOR_QUERY_RULES = {
|
|
841
|
+
"react-doctor/query-destructure-result": "warn",
|
|
842
|
+
"react-doctor/query-mutation-missing-invalidation": "warn",
|
|
843
|
+
"react-doctor/query-no-mutation-in-effect-as-read": "warn",
|
|
844
|
+
"react-doctor/query-no-query-in-effect": "warn",
|
|
845
|
+
"react-doctor/query-no-usequery-for-mutation": "warn"
|
|
846
|
+
};
|
|
847
|
+
var REACT_DOCTOR_MODULE_SCOPE_RULES = {
|
|
848
|
+
"react-doctor/no-unguarded-browser-global-at-module-scope": "warn"
|
|
849
|
+
};
|
|
850
|
+
var REACT_DOCTOR_GUARDRAIL_RULES = {
|
|
851
|
+
"react-doctor/auth-token-in-web-storage": "warn",
|
|
852
|
+
"react-doctor/no-document-write": "warn",
|
|
853
|
+
"react-doctor/no-fill-map-element-as-key": "warn",
|
|
854
|
+
"react-doctor/no-non-literal-selector-query-without-try-catch": "warn",
|
|
855
|
+
"react-doctor/no-non-null-assertion-on-maybe-undefined-result": "warn",
|
|
856
|
+
"react-doctor/no-nullish-coalescing-arithmetic-precedence": "warn",
|
|
857
|
+
"react-doctor/no-path-prefix-containment": "warn",
|
|
858
|
+
"react-doctor/no-secrets-in-client-code": "warn",
|
|
859
|
+
"react-doctor/no-sync-xhr": "warn",
|
|
860
|
+
"react-doctor/no-unescaped-dynamic-string-in-regexp": "warn",
|
|
861
|
+
"react-doctor/no-unguarded-numeric-input-parse": "warn",
|
|
862
|
+
"react-doctor/no-unguarded-throwing-parse-call": "warn",
|
|
863
|
+
"react-doctor/no-whole-object-default-losing-per-key-defaults": "warn",
|
|
864
|
+
"react-doctor/window-open-without-noopener": "warn"
|
|
865
|
+
};
|
|
866
|
+
var REACT_DOCTOR_SHADCN_RULES = {
|
|
867
|
+
"react-doctor/shadcn-dialog-content-requires-title": "warn",
|
|
868
|
+
"react-doctor/shadcn-icon-button-requires-label": "warn",
|
|
869
|
+
"react-doctor/shadcn-input-group-no-raw-controls": "warn",
|
|
870
|
+
"react-doctor/shadcn-tabs-trigger-requires-list": "warn"
|
|
871
|
+
};
|
|
872
|
+
var REACT_DOCTOR_CAPABILITIES = [
|
|
873
|
+
"base-ui",
|
|
874
|
+
"react",
|
|
875
|
+
"react:18",
|
|
876
|
+
"react:19",
|
|
877
|
+
"react:19.2",
|
|
878
|
+
"react-router",
|
|
879
|
+
"react-router:6.4",
|
|
880
|
+
"react-router:6.7",
|
|
881
|
+
"react-router:6.9",
|
|
882
|
+
"react-router:7",
|
|
883
|
+
"react-router:7.8",
|
|
884
|
+
"react-router:7.9",
|
|
885
|
+
"react-router:7.10",
|
|
886
|
+
"react-router:8",
|
|
887
|
+
"react-router-framework",
|
|
888
|
+
"shadcn",
|
|
889
|
+
"ssr",
|
|
890
|
+
"tailwind",
|
|
891
|
+
"tailwind:3.4",
|
|
892
|
+
"tailwind:4",
|
|
893
|
+
"tanstack-query",
|
|
894
|
+
"zod",
|
|
895
|
+
"zod:4"
|
|
896
|
+
];
|
|
897
|
+
|
|
706
898
|
// src/configs/guardrails.ts
|
|
707
899
|
var buildSonarConfig = (sourceDir) => [
|
|
708
900
|
sonarjs.configs.recommended,
|
|
@@ -747,6 +939,14 @@ var buildSonarConfig = (sourceDir) => [
|
|
|
747
939
|
var regexpConfig = [
|
|
748
940
|
{ ...regexp.configs["flat/recommended"], name: "regexp" }
|
|
749
941
|
];
|
|
942
|
+
var reactDoctorGuardrailsConfig = [
|
|
943
|
+
{
|
|
944
|
+
files: ["**/*.ts?(x)", "**/*.js?(x)"],
|
|
945
|
+
name: "react-doctor/guardrails",
|
|
946
|
+
plugins: REACT_DOCTOR_PLUGINS,
|
|
947
|
+
rules: REACT_DOCTOR_GUARDRAIL_RULES
|
|
948
|
+
}
|
|
949
|
+
];
|
|
750
950
|
var cnConditionalConfig = [
|
|
751
951
|
{
|
|
752
952
|
files: ["**/*.ts?(x)", "**/*.js?(x)"],
|
|
@@ -900,6 +1100,7 @@ var buildNoRelativeImportPathsConfig = (sourceDir) => [
|
|
|
900
1100
|
var buildGuardrails = (sourceDir) => [
|
|
901
1101
|
...buildSonarConfig(sourceDir),
|
|
902
1102
|
...regexpConfig,
|
|
1103
|
+
...reactDoctorGuardrailsConfig,
|
|
903
1104
|
...cnConditionalConfig,
|
|
904
1105
|
...noEnumConfig,
|
|
905
1106
|
...noJsxIifeConfig,
|
|
@@ -1054,6 +1255,12 @@ var react = [
|
|
|
1054
1255
|
"react/jsx-curly-brace-presence": "error",
|
|
1055
1256
|
"react/jsx-filename-extension": "off",
|
|
1056
1257
|
"react/jsx-fragments": "error",
|
|
1258
|
+
// airbnb-extended's non-strict react config turns this off, and nothing
|
|
1259
|
+
// else in the bundle catches a list rendered without `key`.
|
|
1260
|
+
"react/jsx-key": [
|
|
1261
|
+
"error",
|
|
1262
|
+
{ checkFragmentShorthand: true, warnOnDuplicates: true }
|
|
1263
|
+
],
|
|
1057
1264
|
// off by default because it doesn't handle props onXyz function names correctly
|
|
1058
1265
|
// turn this on from time to time to check for misnamed handlers elsewhere
|
|
1059
1266
|
"react/jsx-handler-names": ["off", { checkLocalVariables: true }],
|
|
@@ -1127,6 +1334,17 @@ var react = [
|
|
|
1127
1334
|
"@eslint-react/web-api-no-leaked-timeout": "error"
|
|
1128
1335
|
}
|
|
1129
1336
|
},
|
|
1337
|
+
// Curated react-doctor rules (see `react-doctor.ts`, which groups them with
|
|
1338
|
+
// the reason for each group): SSR and hydration hazards, invalid HTML,
|
|
1339
|
+
// accessibility jsx-a11y misses, side effects in render and state updates,
|
|
1340
|
+
// Effect lifecycle and teardown, render-path performance, reduced motion
|
|
1341
|
+
// and font scaling, and component and hook size and complexity.
|
|
1342
|
+
{
|
|
1343
|
+
files: ["**/*.ts?(x)", "**/*.js?(x)"],
|
|
1344
|
+
name: "react-doctor/react",
|
|
1345
|
+
plugins: REACT_DOCTOR_PLUGINS,
|
|
1346
|
+
rules: REACT_DOCTOR_REACT_RULES
|
|
1347
|
+
},
|
|
1130
1348
|
{
|
|
1131
1349
|
files: ["**/*.test.ts?(x)", "**/*.stories.ts?(x)"],
|
|
1132
1350
|
name: "react/test-files",
|
|
@@ -1136,6 +1354,9 @@ var react = [
|
|
|
1136
1354
|
}
|
|
1137
1355
|
];
|
|
1138
1356
|
|
|
1357
|
+
// src/configs/react-router.ts
|
|
1358
|
+
import tanstackQuery from "@tanstack/eslint-plugin-query";
|
|
1359
|
+
|
|
1139
1360
|
// src/plugins/no-imperative-query-fetch.ts
|
|
1140
1361
|
var BANNED_METHODS = /* @__PURE__ */ new Map([
|
|
1141
1362
|
["ensureInfiniteQueryData", "infiniteQuery"],
|
|
@@ -1195,6 +1416,12 @@ var plugin7 = {
|
|
|
1195
1416
|
var no_imperative_query_fetch_default = plugin7;
|
|
1196
1417
|
|
|
1197
1418
|
// src/configs/react-router.ts
|
|
1419
|
+
var TANSTACK_QUERY_RECOMMENDED_RULES = Object.assign(
|
|
1420
|
+
{},
|
|
1421
|
+
...tanstackQuery.configs["flat/recommended"].map(
|
|
1422
|
+
(config) => config.rules
|
|
1423
|
+
)
|
|
1424
|
+
);
|
|
1198
1425
|
var ROUTE_MODULE_EXPORTS = [
|
|
1199
1426
|
"action",
|
|
1200
1427
|
"clientAction",
|
|
@@ -1230,13 +1457,56 @@ var buildReactRouter = (sourceDir) => [
|
|
|
1230
1457
|
]
|
|
1231
1458
|
}
|
|
1232
1459
|
},
|
|
1460
|
+
// TanStack Query, one block: `no-imperative-query-fetch`, the official
|
|
1461
|
+
// `@tanstack/eslint-plugin-query` recommended rules, and the react-doctor
|
|
1462
|
+
// Query rules the official plugin does not cover (`react-doctor.ts`). The
|
|
1463
|
+
// official and react-doctor rules match hooks and clients imported from
|
|
1464
|
+
// TanStack Query, so they stay silent in a project without Query.
|
|
1465
|
+
// `no-imperative-query-fetch` matches by method name.
|
|
1233
1466
|
{
|
|
1234
1467
|
files: [`${sourceDir}/**/*.ts?(x)`],
|
|
1235
1468
|
name: "react-router/no-imperative-query-fetch",
|
|
1236
|
-
plugins: {
|
|
1469
|
+
plugins: {
|
|
1470
|
+
"@tanstack/query": tanstackQuery,
|
|
1471
|
+
"no-imperative-query-fetch": no_imperative_query_fetch_default,
|
|
1472
|
+
...REACT_DOCTOR_PLUGINS
|
|
1473
|
+
},
|
|
1237
1474
|
rules: {
|
|
1475
|
+
...TANSTACK_QUERY_RECOMMENDED_RULES,
|
|
1476
|
+
...REACT_DOCTOR_QUERY_RULES,
|
|
1238
1477
|
"no-imperative-query-fetch/no-imperative-query-fetch": "error"
|
|
1239
1478
|
}
|
|
1479
|
+
},
|
|
1480
|
+
// react-doctor's CLI detects a project's capabilities and hands them to its
|
|
1481
|
+
// rules; the standalone plugin reads them from these settings instead (see
|
|
1482
|
+
// `REACT_DOCTOR_CAPABILITIES`). Route detection matches `app/routes/`,
|
|
1483
|
+
// `app/root.*` and `app/entry.*`.
|
|
1484
|
+
{
|
|
1485
|
+
files: [`${sourceDir}/**/*.ts?(x)`],
|
|
1486
|
+
name: "react-doctor/react-router-settings",
|
|
1487
|
+
settings: {
|
|
1488
|
+
"react-doctor": { capabilities: REACT_DOCTOR_CAPABILITIES }
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
// Curated react-doctor rules (see `react-doctor.ts`) for React Router
|
|
1492
|
+
// framework-mode mistakes that throw or misbehave at runtime: loaders,
|
|
1493
|
+
// actions, middleware, sessions, cookies, search params, navigation, error
|
|
1494
|
+
// UI and the server entry.
|
|
1495
|
+
{
|
|
1496
|
+
files: [`${sourceDir}/**/*.ts?(x)`],
|
|
1497
|
+
name: "react-doctor/react-router",
|
|
1498
|
+
plugins: REACT_DOCTOR_PLUGINS,
|
|
1499
|
+
rules: REACT_DOCTOR_REACT_ROUTER_RULES
|
|
1500
|
+
},
|
|
1501
|
+
// A browser global read at module scope crashes the server render. Source
|
|
1502
|
+
// modules only: tests, stories and the helpers in `tests/` folders run in a
|
|
1503
|
+
// browser or a DOM environment, never through SSR.
|
|
1504
|
+
{
|
|
1505
|
+
files: [`${sourceDir}/**/*.ts?(x)`],
|
|
1506
|
+
ignores: ["**/*.test.ts?(x)", "**/*.stories.ts?(x)", "**/tests/**"],
|
|
1507
|
+
name: "react-doctor/react-router-module-scope",
|
|
1508
|
+
plugins: REACT_DOCTOR_PLUGINS,
|
|
1509
|
+
rules: REACT_DOCTOR_MODULE_SCOPE_RULES
|
|
1240
1510
|
}
|
|
1241
1511
|
];
|
|
1242
1512
|
|
|
@@ -1361,6 +1631,18 @@ var VENDORED_UI_RULES_OFF = {
|
|
|
1361
1631
|
"react-you-might-not-need-an-effect/no-external-store-subscription": "off",
|
|
1362
1632
|
"react-you-might-not-need-an-effect/no-initialize-state": "off",
|
|
1363
1633
|
"react-you-might-not-need-an-effect/no-pass-data-to-parent": "off",
|
|
1634
|
+
// The react-doctor shadcn rules check how GAIA code composes the primitives,
|
|
1635
|
+
// not the primitives themselves. toast.tsx: ToastClose's default
|
|
1636
|
+
// `render={<Button size="icon-sm" />}` reads as an unlabeled icon Button,
|
|
1637
|
+
// but the aria-label sits on the ToastPrimitive.Close that renders it.
|
|
1638
|
+
...Object.fromEntries(
|
|
1639
|
+
Object.keys(REACT_DOCTOR_SHADCN_RULES).map((rule) => [rule, "off"])
|
|
1640
|
+
),
|
|
1641
|
+
// Registry animation, scroll and font classes are shadcn's (toast.tsx
|
|
1642
|
+
// animates without a reduced-motion gate).
|
|
1643
|
+
...Object.fromEntries(
|
|
1644
|
+
Object.keys(MOTION_FONT_RULES).map((rule) => [rule, "off"])
|
|
1645
|
+
),
|
|
1364
1646
|
// sidebar.tsx: SidebarRail's <button> carries no `type`.
|
|
1365
1647
|
"react/button-has-type": "off",
|
|
1366
1648
|
// carousel.tsx, chart.tsx, toggle-group.tsx pass an inline object to a
|
|
@@ -1435,6 +1717,16 @@ var buildShadcn = (sourceDir, opts) => {
|
|
|
1435
1717
|
"shadcn/require-static-classes": "error"
|
|
1436
1718
|
}
|
|
1437
1719
|
},
|
|
1720
|
+
// Curated react-doctor rules (see `react-doctor.ts`) for shadcn
|
|
1721
|
+
// composition that breaks a primitive: an icon-only Button with no label,
|
|
1722
|
+
// a DialogContent with no title, a raw control in an InputGroup, a
|
|
1723
|
+
// TabsTrigger outside TabsList.
|
|
1724
|
+
{
|
|
1725
|
+
files: [`${sourceDir}/**/*.${SOURCE_EXTENSIONS}`],
|
|
1726
|
+
name: "react-doctor/shadcn",
|
|
1727
|
+
plugins: REACT_DOCTOR_PLUGINS,
|
|
1728
|
+
rules: REACT_DOCTOR_SHADCN_RULES
|
|
1729
|
+
},
|
|
1438
1730
|
{
|
|
1439
1731
|
files: uiFiles,
|
|
1440
1732
|
name: "shadcn/vendored-ui",
|
|
@@ -1562,6 +1854,43 @@ var perfectionistConfig = [
|
|
|
1562
1854
|
type: "natural"
|
|
1563
1855
|
}
|
|
1564
1856
|
],
|
|
1857
|
+
// TanStack Query reads option order for type inference: the
|
|
1858
|
+
// `@tanstack/query/*-property-order` rules need `onMutate` before
|
|
1859
|
+
// `onError` and `onSettled`, and `queryFn` before the page-param
|
|
1860
|
+
// getters. Plain natural order puts those keys first, so in the object
|
|
1861
|
+
// passed straight to the TanStack call they sort into a last group
|
|
1862
|
+
// instead, natural order everywhere else in it. Every other object
|
|
1863
|
+
// keeps `recommended-natural`'s sort.
|
|
1864
|
+
"perfectionist/sort-objects": [
|
|
1865
|
+
"error",
|
|
1866
|
+
{
|
|
1867
|
+
customGroups: [
|
|
1868
|
+
{
|
|
1869
|
+
elementNamePattern: "^(onError|onSettled)$",
|
|
1870
|
+
groupName: "after-on-mutate"
|
|
1871
|
+
}
|
|
1872
|
+
],
|
|
1873
|
+
groups: ["unknown", "after-on-mutate"],
|
|
1874
|
+
order: "asc",
|
|
1875
|
+
type: "natural",
|
|
1876
|
+
useConfigurationIf: { callingFunctionNamePattern: "^useMutation$" }
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
customGroups: [
|
|
1880
|
+
{
|
|
1881
|
+
elementNamePattern: "^(getNextPageParam|getPreviousPageParam)$",
|
|
1882
|
+
groupName: "after-query-fn"
|
|
1883
|
+
}
|
|
1884
|
+
],
|
|
1885
|
+
groups: ["unknown", "after-query-fn"],
|
|
1886
|
+
order: "asc",
|
|
1887
|
+
type: "natural",
|
|
1888
|
+
useConfigurationIf: {
|
|
1889
|
+
callingFunctionNamePattern: "^(infiniteQueryOptions|useInfiniteQuery|useSuspenseInfiniteQuery)$"
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1892
|
+
{ order: "asc", type: "natural" }
|
|
1893
|
+
],
|
|
1565
1894
|
"perfectionist/sort-jsx-props": [
|
|
1566
1895
|
"error",
|
|
1567
1896
|
{
|