oxlint-plugin-react-doctor 0.9.4-dev.25dbf6d → 0.9.4-dev.459c5d3

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/dist/core.js CHANGED
@@ -10,5 +10,5 @@ useEffect(() => {
10
10
  setCount((currentCount) => currentCount + 1);
11
11
  }, []);
12
12
 
13
- If the missing value is recreated every render, move it inside the hook or stabilize it before adding it to deps.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/expo-no-non-inlined-env`,id:`expo-no-non-inlined-env`,source:`react-doctor`,originallyExternal:!1,rule:{id:`expo-no-non-inlined-env`,title:`Non-inlinable process.env access (Expo)`,severity:`warn`,recommendation:"Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",category:`Bugs`,framework:`react-native`,requires:[`expo`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/fieldset-requires-legend`,id:`fieldset-requires-legend`,source:`react-doctor`,originallyExternal:!1,rule:{id:`fieldset-requires-legend`,title:`Fieldset group has no legend`,severity:`warn`,recommendation:`Name grouped controls with a direct legend, or an explicit accessible name when no visible group label is appropriate.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/firebase-client-owned-authz-field`,id:`firebase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`firebase-client-owned-authz-field`,title:`Client writes authorization field`,severity:`error`,recommendation:`Derive authority fields on the server or enforce them in Firebase/Supabase rules; never trust client-provided owner, org, or role values.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/firebase-permissive-rules`,id:`firebase-permissive-rules`,source:`react-doctor`,originallyExternal:!1,rule:{id:`firebase-permissive-rules`,title:`Permissive Firebase security rule`,severity:`error`,recommendation:"Bind every read/write to `request.auth.uid`, immutable ownership, and tenant membership instead of treating sign-in as authorization.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/firebase-query-filter-as-auth`,id:`firebase-query-filter-as-auth`,source:`react-doctor`,originallyExternal:!1,rule:{id:`firebase-query-filter-as-auth`,title:`Firestore query filter used as authorization`,severity:`warn`,recommendation:"Make sure Firestore rules compare the requested document against `request.auth.uid` and trusted membership data.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/forbid-component-props`,id:`forbid-component-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forbid-component-props`,title:`Blocked component prop bypasses API contract`,severity:`warn`,recommendation:`Configure blocked component props so callers cannot bypass the component API contract.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/forbid-dom-props`,id:`forbid-dom-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forbid-dom-props`,title:`Blocked DOM prop bypasses project contract`,severity:`warn`,recommendation:`Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/forbid-elements`,id:`forbid-elements`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forbid-elements`,title:`Blocked element bypasses approved UI primitives`,severity:`warn`,recommendation:`Configure blocked elements so code stays on the approved UI primitives.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/form-control-requires-name`,id:`form-control-requires-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`form-control-requires-name`,title:`Form control is omitted from named submission data`,severity:`warn`,recommendation:`Give each data-bearing native control inside a form a stable name for FormData, autofill, and non-JavaScript submission.`,category:`Bugs`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/forward-ref-uses-ref`,id:`forward-ref-uses-ref`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forward-ref-uses-ref`,title:`forwardRef without ref parameter`,severity:`warn`,recommendation:"Accept the `ref` parameter or drop `forwardRef` so parents are not promised a ref that never reaches the node.",category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/git-provider-url-injection-risk`,id:`git-provider-url-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`git-provider-url-injection-risk`,title:`Git provider URL built from interpolation`,severity:`warn`,recommendation:`Validate owner, repo, org, and branch identifiers against strict slugs and build URLs with URL/path encoders instead of raw interpolation.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/heading-has-content`,id:`heading-has-content`,source:`react-doctor`,originallyExternal:!0,rule:{id:`heading-has-content`,title:`Heading has no content`,severity:`warn`,recommendation:`Put readable text in every heading.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/hook-import-rename-loses-use-prefix`,id:`hook-import-rename-loses-use-prefix`,source:`react-doctor`,originallyExternal:!1,rule:{id:`hook-import-rename-loses-use-prefix`,title:`Hook import alias disables hook lint checks`,severity:`warn`,recommendation:"Keep a recognised `use*` name in custom-hook aliases, reserve bare `use` for React's `use` hook, and do not rename React hooks with name-specific lint semantics such as `useEffect`, `useImperativeHandle`, or `useEffectEvent`.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/hook-use-state`,id:`hook-use-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`hook-use-state`,title:`useState not destructured`,severity:`warn`,recommendation:"Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/hooks-no-nan-in-deps`,id:`hooks-no-nan-in-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`hooks-no-nan-in-deps`,title:`NaN in a hook dependency array`,severity:`warn`,recommendation:"Remove `NaN` (or `Number.NaN`) from the dependency array. If a value can be NaN at runtime, normalise it (`Number.isNaN(x) ? 0 : x`) before passing it.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/html-has-lang`,id:`html-has-lang`,source:`react-doctor`,originallyExternal:!0,rule:{id:`html-has-lang`,title:`html element missing lang`,severity:`warn`,recommendation:'Set `<html lang="…">` so screen readers know the page language.',category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/html-label-has-single-control`,id:`html-label-has-single-control`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-label-has-single-control`,title:`Label wraps multiple native controls`,severity:`warn`,recommendation:`Associate each label with one native control; use fieldset and legend to name a group of controls.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-invalid-paragraph-child`,id:`html-no-invalid-paragraph-child`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-invalid-paragraph-child`,title:`Block element inside a paragraph`,severity:`warn`,recommendation:"Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-invalid-table-nesting`,id:`html-no-invalid-table-nesting`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-invalid-table-nesting`,title:`Invalid table element nesting`,severity:`warn`,recommendation:"Put each table element in its required parent: `<thead>`/`<tbody>`/`<tfoot>` directly inside `<table>`, `<tr>` inside a row group, `<td>`/`<th>` inside `<tr>`. Browsers quietly fix broken table markup, so write it to spec.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-nested-form`,id:`html-no-nested-form`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-nested-form`,title:`Form nested inside another form`,severity:`warn`,recommendation:`Keep forms separate, or make the inner action a button associated with the outer form. HTML does not permit form descendants inside a form.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-nested-interactive`,id:`html-no-nested-interactive`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-nested-interactive`,title:`Interactive control contains another focusable control`,severity:`warn`,recommendation:`Move the inner control beside its interactive ancestor so each action has independent semantics and focus behavior.`,category:`Accessibility`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-xml-lang-mismatch`,id:`html-xml-lang-mismatch`,source:`react-doctor`,originallyExternal:!0,rule:{id:`html-xml-lang-mismatch`,title:`Conflicting document language declarations`,severity:`warn`,recommendation:`Use matching base languages for lang and xml:lang on the root html element.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/iframe-has-title`,id:`iframe-has-title`,source:`react-doctor`,originallyExternal:!0,rule:{id:`iframe-has-title`,title:`iframe missing title`,severity:`warn`,recommendation:"Add a descriptive `title` so screen reader users know what the embedded frame contains.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/iframe-missing-sandbox`,id:`iframe-missing-sandbox`,source:`react-doctor`,originallyExternal:!0,rule:{id:`iframe-missing-sandbox`,title:`iframe missing sandbox attribute`,severity:`warn`,recommendation:'Add `sandbox=""` or a curated value so embedded pages cannot get full access to your site by default.',category:`Security`,framework:`global`,requires:[`react`],matchByOccurrence:!0,isScanRule:!1}},{key:`react-doctor/iframe-title-unique`,id:`iframe-title-unique`,source:`react-doctor`,originallyExternal:!0,rule:{id:`iframe-title-unique`,title:`Frame title is duplicated`,severity:`warn`,recommendation:`Give each frame a concise title that distinguishes its purpose from sibling frames.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/img-redundant-alt`,id:`img-redundant-alt`,source:`react-doctor`,originallyExternal:!0,rule:{id:`img-redundant-alt`,title:`Redundant words in image alt`,severity:`warn`,recommendation:`Do not put 'image' or 'photo' in alt text. Describe what is shown.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/import-metadata-execution-risk`,id:`import-metadata-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`import-metadata-execution-risk`,title:`Imported metadata reaches code execution`,severity:`error`,recommendation:`Parse imported metadata as data with strict schemas; do not evaluate EXIF, manifests, presets, dropped files, or archives.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/ink-ctrl-c-handler-requires-exit-option`,id:`ink-ctrl-c-handler-requires-exit-option`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-ctrl-c-handler-requires-exit-option`,title:`Ctrl-C handler is unreachable`,severity:`error`,recommendation:"Pass `{exitOnCtrlC: false}` to `render()` before handling Ctrl-C with `useInput`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-newline-inside-text`,id:`ink-newline-inside-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-newline-inside-text`,title:`Retired: Ink Newline placement`,severity:`error`,recommendation:"No change is required; Ink supports standalone `<Newline>` elements.",category:`Bugs`,framework:`global`,tags:[`ink`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/ink-no-bare-process-exit`,id:`ink-no-bare-process-exit`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-bare-process-exit`,title:`Process exits before Ink cleanup`,severity:`error`,recommendation:"Use Ink's `useApp().exit()` so Ink restores terminal state.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-direct-raw-mode`,id:`ink-no-direct-raw-mode`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-direct-raw-mode`,title:`Raw mode changed during render`,severity:`error`,recommendation:"Move `useStdin().setRawMode()` to an effect and restore it in cleanup.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-dom-host-elements`,id:`ink-no-dom-host-elements`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-dom-host-elements`,title:`DOM element used in an Ink tree`,severity:`error`,recommendation:"Use Ink primitives such as `<Box>` and `<Text>` instead of DOM host elements.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-dom-router`,id:`ink-no-dom-router`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-dom-router`,title:`DOM router used in an Ink tree`,severity:`error`,recommendation:`Use React Router's memory router APIs in Ink applications.`,category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-focus-in-render`,id:`ink-no-focus-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-focus-in-render`,title:`Ink focus changed during render`,severity:`error`,recommendation:`Move focus-manager calls to an effect or input handler.`,category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-layout-inside-text`,id:`ink-no-layout-inside-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-layout-inside-text`,title:`Layout component nested in Ink Text`,severity:`error`,recommendation:"Move Ink layout components outside `<Text>` and `<Transform>` boundaries.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-live-hooks-in-render-to-string`,id:`ink-no-live-hooks-in-render-to-string`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-live-hooks-in-render-to-string`,title:`Inert input hook used during string rendering`,severity:`error`,recommendation:"Keep input subscriptions out of components used only by `renderToString()`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-measure-element-in-render`,id:`ink-no-measure-element-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-measure-element-in-render`,title:`Ink element measured during render`,severity:`error`,recommendation:`Measure Ink elements in a layout effect, effect, callback ref, or event handler.`,category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-multiple-static`,id:`ink-no-multiple-static`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-multiple-static`,title:`Multiple unconditional Static regions in one render root`,severity:`warn`,recommendation:"Combine unconditional output in one render root into a single `<Static>`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-raw-text`,id:`ink-no-raw-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-raw-text`,title:`Raw text outside Ink Text`,severity:`error`,recommendation:"Wrap terminal text in Ink's `<Text>` component.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-repeated-render`,id:`ink-no-repeated-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-repeated-render`,title:`Ink render reused before unmount`,severity:`error`,recommendation:"Call Ink `render()` once and update or rerender the returned instance.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-prefer-use-animation`,id:`ink-prefer-use-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-prefer-use-animation`,title:`Animation loop implemented with setInterval`,severity:`warn`,recommendation:"Use Ink's shared `useAnimation()` scheduler and automatic unmount cleanup.",category:`Performance`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-prefer-use-paste`,id:`ink-prefer-use-paste`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-prefer-use-paste`,title:`Paste interpreted through useInput`,severity:`warn`,recommendation:"Use Ink's `usePaste()` for bracketed multi-character paste input.",category:`Bugs`,framework:`global`,tags:[`ink`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/ink-static-is-append-only`,id:`ink-static-is-append-only`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-static-is-append-only`,title:`Static receives a non-append-only collection`,severity:`warn`,recommendation:"Pass `<Static>` a collection whose existing entries never reorder or disappear.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-static-requires-key`,id:`ink-static-requires-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-static-requires-key`,title:`Static item root missing a key`,severity:`error`,recommendation:"Put a stable `key` on the root element returned by `<Static>`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-suspense-requires-concurrent`,id:`ink-suspense-requires-concurrent`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-suspense-requires-concurrent`,title:`Retired: Ink Suspense rendering mode`,severity:`error`,recommendation:`No change is required; Ink can render Suspense fallbacks without concurrency.`,category:`Bugs`,framework:`global`,tags:[`ink`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/ink-use-reactive-window-size`,id:`ink-use-reactive-window-size`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-use-reactive-window-size`,title:`Terminal dimensions read non-reactively`,severity:`warn`,recommendation:"Use Ink's `useWindowSize()` so resize events trigger a render.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-use-string-width-for-cursor`,id:`ink-use-string-width-for-cursor`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-use-string-width-for-cursor`,title:`String length used as terminal column width`,severity:`warn`,recommendation:"Measure terminal columns with `string-width` before calling `setCursorPosition`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-use-suspend-terminal`,id:`ink-use-suspend-terminal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-use-suspend-terminal`,title:`Interactive child process bypasses Ink suspension`,severity:`error`,recommendation:"Run inherited-TTY child processes inside `useApp().suspendTerminal()`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-valid-aria-semantics`,id:`ink-valid-aria-semantics`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-valid-aria-semantics`,title:`Invalid Ink accessibility semantics`,severity:`error`,recommendation:`Use a role supported by Ink and do not label elements hidden from screen readers.`,category:`Accessibility`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/insecure-crypto-risk`,id:`insecure-crypto-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`insecure-crypto-risk`,title:`Weak cryptography in security context`,severity:`warn`,recommendation:"Use modern primitives, `crypto.randomBytes` / Web Crypto randomness, and timing-safe comparisons for signatures, digests, tokens, and auth material.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/insecure-session-cookie`,id:`insecure-session-cookie`,source:`react-doctor`,originallyExternal:!1,rule:{id:`insecure-session-cookie`,title:`Auth cookie missing HttpOnly protection`,severity:`warn`,recommendation:"Set auth/session cookies server-side with `httpOnly: true`, `secure: true`, and `sameSite`. Cookies set via `document.cookie` or with `httpOnly: false` are readable by any XSS payload and can be stolen.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/interactive-supports-focus`,id:`interactive-supports-focus`,source:`react-doctor`,originallyExternal:!0,rule:{id:`interactive-supports-focus`,title:`Interactive element not focusable`,severity:`warn`,recommendation:`Add keyboard focus support so users can reach interactive elements without a pointer.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jotai-derived-atom-returns-fresh-object`,id:`jotai-derived-atom-returns-fresh-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jotai-derived-atom-returns-fresh-object`,title:`Derived atom returns fresh object`,severity:`warn`,recommendation:"Split it into one derived atom per field (each compares cleanly with Object.is), or wrap with `selectAtom(source, fn, shallow)` from jotai/utils if you really need one object.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jotai-select-atom-in-render-body`,id:`jotai-select-atom-in-render-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jotai-select-atom-in-render-body`,title:`selectAtom called during render`,severity:`error`,recommendation:"Lift `selectAtom(base, fn)` to module scope, or wrap it: `const atom = useMemo(() => selectAtom(base, fn), [deps])`. Calling it during render makes a new atom every time and re-subscribes forever.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jotai-tq-use-raw-query-atom`,id:`jotai-tq-use-raw-query-atom`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jotai-tq-use-raw-query-atom`,title:`Subscribing to raw query atom`,severity:`warn`,recommendation:"Derive the field you read: `const dataAtom = atom((get) => get(queryAtom).data)`. Subscribing to the whole query atom re-renders on every refetch, focus, or no-op cache hit.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/js-async-reduce-without-awaited-acc`,id:`js-async-reduce-without-awaited-acc`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-async-reduce-without-awaited-acc`,title:`Async reduce drops its accumulator`,severity:`warn`,recommendation:"Await the accumulator first: `const acc = await previous; ...; return acc;`. Seed it with `Promise.resolve(initial)` so the first run also gets a Promise",category:`Performance`,framework:`global`,isScanRule:!1}},{key:`react-doctor/js-batch-dom-css`,id:`js-batch-dom-css`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-batch-dom-css`,title:`Repeated inline style writes`,severity:`warn`,recommendation:`Do all your reads first, then all your writes. Mixing them inside a loop makes the browser recalculate the layout again and again, which is slow`,category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-cache-property-access`,id:`js-cache-property-access`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-cache-property-access`,title:`Repeated property access in a loop`,severity:`warn`,recommendation:"Read the value once into a variable at the top of the loop: `const { x, y } = obj.deeply.nested`",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-cache-storage`,id:`js-cache-storage`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-cache-storage`,title:`Repeated localStorage reads`,severity:`warn`,recommendation:"Read `localStorage`/`sessionStorage` once and reuse the value. Every read has to parse the data again, which is slow",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-combine-iterations`,id:`js-combine-iterations`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-combine-iterations`,title:`Chained array iterations`,severity:`warn`,recommendation:"Combine `.map().filter()` style chains into one pass with `.reduce()` or a `for...of` loop, so you only loop over the list once",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-early-exit`,id:`js-early-exit`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-early-exit`,title:`Deeply nested if statements`,severity:`warn`,recommendation:"Add an early `return` or `continue` so the deep nesting flattens out and you bail as soon as the answer is known",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-flatmap-filter`,id:`js-flatmap-filter`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-flatmap-filter`,title:`.map().filter(Boolean) loops twice`,severity:`warn`,recommendation:"Use `.flatMap(item => condition ? [value] : [])` to change and drop items in one pass, instead of building a throwaway array in between",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-hoist-intl`,id:`js-hoist-intl`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-hoist-intl`,title:`Intl formatter rebuilt each call`,severity:`warn`,recommendation:"Move `new Intl.NumberFormat(...)` to the top of the file or wrap it in `useMemo`. Building one is slow, so don't redo it on every call",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-hoist-regexp`,id:`js-hoist-regexp`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-hoist-regexp`,title:`RegExp built inside a loop`,severity:`warn`,recommendation:"Move `new RegExp(...)` (or large regex literals) to a constant outside the loop so it isn't rebuilt on every pass",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-index-maps`,id:`js-index-maps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-index-maps`,title:`array.find() inside a loop`,severity:`warn`,recommendation:"Build a `Map` once before the loop instead of calling `array.find(...)` inside it",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-length-check-first`,id:`js-length-check-first`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-length-check-first`,title:`Array compare without length check`,severity:`warn`,recommendation:"Check `a.length === b.length && a.every((x, i) => x === b[i])` so arrays of different sizes stop right away",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-min-max-loop`,id:`js-min-max-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-min-max-loop`,title:`sort() to find min or max`,severity:`warn`,recommendation:"Use `Math.min(...array)` or `Math.max(...array)` instead of sorting the whole list just to read the first or last item",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-set-map-lookups`,id:`js-set-map-lookups`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-set-map-lookups`,title:`Array lookup inside a loop`,severity:`warn`,recommendation:"Use a `Set` or `Map` when you check for the same items over and over. `Array.includes`/`find` scans the whole list each time",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-tosorted-immutable`,id:`js-tosorted-immutable`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-tosorted-immutable`,title:`Spread copy before sort()`,severity:`warn`,recommendation:"Use `array.toSorted()` (ES2023) instead of `[...array].sort()` so you sort without copying the array first",category:`Performance`,framework:`global`,disabledWhen:[`react-native`,`pre-es2023`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/jsx-boolean-value`,id:`jsx-boolean-value`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-boolean-value`,title:`Inconsistent boolean prop notation`,severity:`warn`,recommendation:`Use one boolean-attribute style so equivalent true props scan the same across the codebase.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-curly-brace-presence`,id:`jsx-curly-brace-presence`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-curly-brace-presence`,title:`Unnecessary curly braces in JSX`,severity:`warn`,recommendation:`Use one JSX literal style so equivalent markup scans the same without extra JSX noise.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-filename-extension`,id:`jsx-filename-extension`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-filename-extension`,title:`JSX in disallowed file extension`,severity:`warn`,recommendation:`Name JSX files with the configured extension so file names accurately signal which files contain JSX.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-fragments`,id:`jsx-fragments`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-fragments`,title:`Inconsistent fragment syntax`,severity:`warn`,recommendation:`Use one fragment style so identical wrappers do not look different across files.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-handler-names`,id:`jsx-handler-names`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-handler-names`,title:`Inconsistent event handler names`,severity:`warn`,recommendation:"Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-key`,id:`jsx-key`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-key`,title:`Missing key in list`,severity:`error`,recommendation:"Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-max-depth`,id:`jsx-max-depth`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-max-depth`,title:`JSX nested too deeply`,severity:`warn`,recommendation:`Pull deeply nested JSX into smaller components so it's easier to read.`,category:`Maintainability`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/jsx-no-comment-textnodes`,id:`jsx-no-comment-textnodes`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-comment-textnodes`,title:`Comment rendered as JSX text`,severity:`warn`,recommendation:"Wrap JSX comments in `{/* … */}` so users do not see comment text rendered as children.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-constructed-context-values`,id:`jsx-no-constructed-context-values`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-constructed-context-values`,title:`Unstable context provider value`,severity:`warn`,recommendation:"Wrap the context value in `useMemo` or move it outside the component so consumers do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-duplicate-props`,id:`jsx-no-duplicate-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-duplicate-props`,title:`Duplicate props on element`,severity:`error`,recommendation:`Remove or rename one of the duplicate props so the later value does not silently override the earlier one.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-jsx-as-prop`,id:`jsx-no-jsx-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-jsx-as-prop`,title:`JSX element passed as a prop`,severity:`warn`,recommendation:"Move the JSX outside the component or wrap it in `useMemo` so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-new-array-as-prop`,id:`jsx-no-new-array-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-new-array-as-prop`,title:`New array passed as a prop`,severity:`warn`,recommendation:"Wrap the array in `useMemo` or move it outside the component so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-new-function-as-prop`,id:`jsx-no-new-function-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-new-function-as-prop`,title:`New function passed as a prop`,severity:`warn`,recommendation:"Wrap the callback in `useCallback` or move it outside the component so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-new-object-as-prop`,id:`jsx-no-new-object-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-new-object-as-prop`,title:`New object passed as a prop`,severity:`warn`,recommendation:"Wrap the object in `useMemo` or move it outside the component so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-script-url`,id:`jsx-no-script-url`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-script-url`,title:`javascript: URL in JSX`,severity:`error`,recommendation:"Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",category:`Security`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-target-blank`,id:`jsx-no-target-blank`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-target-blank`,title:`Unsafe target=_blank link`,severity:`warn`,recommendation:'Add `rel="noreferrer"` (or `"noopener"`) when using `target="_blank"`.',category:`Security`,framework:`global`,requires:[`react`,`target-blank-needs-explicit-protection`],isScanRule:!1}},{key:`react-doctor/jsx-no-undef`,id:`jsx-no-undef`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-undef`,title:`Undefined JSX component`,severity:`error`,recommendation:`Import the component or fix the typo so React can resolve the JSX identifier at runtime.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-useless-fragment`,id:`jsx-no-useless-fragment`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-useless-fragment`,title:`Unnecessary React fragment`,severity:`warn`,recommendation:`Drop the fragment when it wraps a single child or sits directly under an HTML tag.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-numeric-and-leaked-render`,id:`jsx-numeric-and-leaked-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jsx-numeric-and-leaked-render`,title:`Numeric && renders a stray 0`,severity:`warn`,recommendation:"In `{items.length && <List/>}` React renders a literal `0` when the count is 0. Compare explicitly (`items.length > 0 && <List/>`) or use a ternary (`items.length ? <List/> : null`).",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/jsx-pascal-case`,id:`jsx-pascal-case`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-pascal-case`,title:`Component name not PascalCase`,severity:`warn`,recommendation:`Rename custom JSX components to PascalCase so React treats them as components, not intrinsic DOM tags.`,category:`Maintainability`,framework:`global`,requires:[`react`],tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-props-no-spread-multi`,id:`jsx-props-no-spread-multi`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-props-no-spread-multi`,title:`Same prop spread multiple times`,severity:`warn`,recommendation:`Spread each value at most once so later props cannot silently override earlier props from the same object.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-props-no-spreading`,id:`jsx-props-no-spreading`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-props-no-spreading`,title:`Props spread onto element`,severity:`warn`,recommendation:`List each prop explicitly so consumers can see the component API instead of receiving hidden spread props.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jwt-insecure-verification`,id:`jwt-insecure-verification`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jwt-insecure-verification`,title:`JWT verified with the 'none' algorithm`,severity:`error`,recommendation:"Never accept the `none` algorithm; it disables signature verification and lets any forged token through. Pin the real algorithm(s) explicitly (`jwt.verify(token, key, { algorithms: ['RS256'] })`).",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/key-lifecycle-risk`,id:`key-lifecycle-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`key-lifecycle-risk`,title:`Long-lived key material in repository`,severity:`error`,recommendation:`Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/label-has-associated-control`,id:`label-has-associated-control`,source:`react-doctor`,originallyExternal:!0,rule:{id:`label-has-associated-control`,title:`Label missing associated control`,severity:`warn`,recommendation:"Tie every label to a control with `htmlFor`, or by nesting the input.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/lang`,id:`lang`,source:`react-doctor`,originallyExternal:!0,rule:{id:`lang`,title:`Invalid lang attribute value`,severity:`warn`,recommendation:"Use a valid language code like `en` or `en-US` so screen readers choose the right pronunciation rules.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/loading-action-preserves-trigger`,id:`loading-action-preserves-trigger`,source:`react-doctor`,originallyExternal:!1,rule:{id:`loading-action-preserves-trigger`,title:`Loading state removes its initiating action`,severity:`warn`,recommendation:`Keep the initiating control mounted while work is pending. Disable it or mark it busy, and put the spinner or status inside the same control so focus and action identity remain stable.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/local-rpc-native-bridge-risk`,id:`local-rpc-native-bridge-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`local-rpc-native-bridge-risk`,title:`Weak localhost native bridge boundary`,severity:`warn`,recommendation:`Use exact origin allowlists after URL parsing, per-request nonces, narrow methods, and never expose install/update commands to arbitrary web pages.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/mcp-tool-capability-risk`,id:`mcp-tool-capability-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mcp-tool-capability-risk`,title:`MCP tool exposes dangerous capability`,severity:`warn`,recommendation:`MCP tool calls run with the connecting client's authority. Validate inputs, enforce per-tool authorization, and avoid raw filesystem/shell/network access where possible.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/mdx-ssr-execution-risk`,id:`mdx-ssr-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mdx-ssr-execution-risk`,title:`Server-rendered MDX can execute code`,severity:`warn`,recommendation:`Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/media-has-caption`,id:`media-has-caption`,source:`react-doctor`,originallyExternal:!0,rule:{id:`media-has-caption`,title:`Media missing captions`,severity:`warn`,recommendation:'Add `<track kind="captions">` inside every `<audio>` / `<video>`.',category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/mobx-no-make-auto-observable-in-inheritance`,id:`mobx-no-make-auto-observable-in-inheritance`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mobx-no-make-auto-observable-in-inheritance`,title:`Unsupported MobX auto-observable inheritance`,severity:`error`,recommendation:"Replace inheritance with composition, or annotate inherited members explicitly with `makeObservable`.",category:`Bugs`,framework:`global`,requires:[`mobx:6`],isScanRule:!1}},{key:`react-doctor/mobx-no-observer-wrapped-memo`,id:`mobx-no-observer-wrapped-memo`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mobx-no-observer-wrapped-memo`,title:`Invalid MobX observer wrapper order`,severity:`error`,recommendation:"Pass the component directly to `observer`, or apply React `memo` outside the resulting observer component.",category:`Bugs`,framework:`global`,requires:[`mobx:4`,`mobx-react-binding-observer-memo-guard`,`react`],isScanRule:!1}},{key:`react-doctor/mobx-reaction-disposer-discarded`,id:`mobx-reaction-disposer-discarded`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mobx-reaction-disposer-discarded`,title:`MobX reaction disposer discarded`,severity:`warn`,recommendation:"Keep the disposer returned by `reaction` or `autorun` and invoke it during teardown.",category:`Bugs`,framework:`global`,requires:[`mobx:4`],isScanRule:!1}},{key:`react-doctor/motion-animate-presence-must-outlive-child`,id:`motion-animate-presence-must-outlive-child`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-animate-presence-must-outlive-child`,title:`AnimatePresence unmounts with its exiting child`,severity:`warn`,recommendation:`Keep AnimatePresence mounted and place the condition around its child so Motion can observe and animate the child leaving.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-animate-presence-requires-key`,id:`motion-animate-presence-requires-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-animate-presence-requires-key`,title:`AnimatePresence child is missing a key`,severity:`warn`,recommendation:`Give every direct AnimatePresence child a stable key derived from its identity so Motion can track which element exits.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-animate-presence-wait-single-child`,id:`motion-animate-presence-wait-single-child`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-animate-presence-wait-single-child`,title:`AnimatePresence wait mode has multiple children`,severity:`warn`,recommendation:'Render one direct child at a time in `mode="wait"`, or use `sync` or `popLayout` when several children can be present.',category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-create-in-render`,id:`motion-create-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-create-in-render`,title:`Motion component is created during render`,severity:`warn`,recommendation:`Create Motion components at module scope, or memoize a genuinely dynamic component factory so its identity remains stable.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-drag-axis-constraint-mismatch`,id:`motion-drag-axis-constraint-mismatch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-drag-axis-constraint-mismatch`,title:`Motion drag constraints do not match the drag axis`,severity:`warn`,recommendation:`Constrain horizontal drags with left or right bounds and vertical drags with top or bottom bounds, keeping paired bounds in ascending order.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-imperative-animation-in-render`,id:`motion-imperative-animation-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-imperative-animation-in-render`,title:`Imperative Motion animation runs during render`,severity:`error`,recommendation:`Start imperative animations from an effect or user interaction, and derive render output declaratively from props and state.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-keyframe-times-mismatch`,id:`motion-keyframe-times-mismatch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-keyframe-times-mismatch`,title:`Motion keyframes and times have different lengths`,severity:`error`,recommendation:`Provide one transition time for each keyframe so Motion can place every value on the animation timeline.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-layout-on-inline-element`,id:`motion-layout-on-inline-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-layout-on-inline-element`,title:`Motion layout animation targets an inline element`,severity:`warn`,recommendation:`Use inline-block, block, flex, or grid so the browser can apply Motion's transform-based layout animation.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-unstable-layout-id-in-iteration`,id:`motion-unstable-layout-id-in-iteration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-unstable-layout-id-in-iteration`,title:`Motion layout ID is unstable in an iteration`,severity:`warn`,recommendation:`Derive repeated layout IDs from stable item identity, or scope each repeated component with a LayoutGroup ID derived from the item.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-use-transform-range-length`,id:`motion-use-transform-range-length`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-use-transform-range-length`,title:`Motion transform ranges have different lengths`,severity:`error`,recommendation:`Give useTransform input and output ranges the same number of entries so every input stop has an output value.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-value-constructor-in-render`,id:`motion-value-constructor-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-value-constructor-in-render`,title:`Motion value is recreated during render`,severity:`warn`,recommendation:`Use useMotionValue inside React components, or create a manual motionValue outside the render path.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/motion-value-subscription-in-render`,id:`motion-value-subscription-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-value-subscription-in-render`,title:`Motion value subscription is created during render`,severity:`error`,recommendation:`Subscribe with useMotionValueEvent, or create the subscription in an effect and return its cleanup function.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/mouse-events-have-key-events`,id:`mouse-events-have-key-events`,source:`react-doctor`,originallyExternal:!0,rule:{id:`mouse-events-have-key-events`,title:`Mouse handler missing focus handler`,severity:`warn`,recommendation:`Pair mouse events with keyboard ones so keyboard users are not left out.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/nextjs-async-client-component`,id:`nextjs-async-client-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-async-client-component`,title:`Async client component`,severity:`error`,recommendation:`Fetch data in a parent Server Component and pass it as props, or use useQuery/useSWR in the client component`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-async-dynamic-api-not-awaited`,id:`nextjs-async-dynamic-api-not-awaited`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-async-dynamic-api-not-awaited`,title:`Synchronous Next.js request API access`,severity:`error`,recommendation:"Await `cookies()`, `headers()`, `draftMode()`, and async route `params` or `searchParams`, or unwrap their promises with React `use()`, before reading properties.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs:15`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-error-boundary-missing-use-client`,id:`nextjs-error-boundary-missing-use-client`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-error-boundary-missing-use-client`,title:`Error boundary missing 'use client'`,severity:`error`,recommendation:"Add `'use client'` at the top of this file. Error boundaries must be Client Components to catch and render fallback UI",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-global-error-missing-html-body`,id:`nextjs-global-error-missing-html-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-global-error-missing-html-body`,title:`global-error.tsx missing <html>/<body>`,severity:`error`,recommendation:"Wrap your error UI in `<html><body>...</body></html>`. The root layout is unmounted when global-error renders",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-image-missing-sizes`,id:`nextjs-image-missing-sizes`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-image-missing-sizes`,title:`next/image fill image is missing sizes`,severity:`warn`,recommendation:"Add `sizes` matching your layout so `next/image` does not assume the largest candidate and make users download oversized images.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-inline-script-missing-id`,id:`nextjs-inline-script-missing-id`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-inline-script-missing-id`,title:`Inline script missing id`,severity:`warn`,recommendation:'Add `id="descriptive-name"` so Next.js can track, deduplicate, and re-execute the script correctly',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-metadata-url-consistency`,id:`nextjs-metadata-url-consistency`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-metadata-url-consistency`,title:`Canonical and Open Graph URLs disagree`,severity:`warn`,recommendation:`Use the same preferred page URL for alternates.canonical and openGraph.url so search and social previews identify one page.`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],isScanRule:!1}},{key:`react-doctor/nextjs-missing-metadata`,id:`nextjs-missing-metadata`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-missing-metadata`,title:`Page missing metadata for search previews`,severity:`warn`,recommendation:"Add metadata or `generateMetadata()` so search engines and social previews get a title and description.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-a-element`,id:`nextjs-no-a-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-a-element`,title:`Plain anchor reloads internal Next.js links`,severity:`warn`,recommendation:"`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-client-fetch-for-server-data`,id:`nextjs-no-client-fetch-for-server-data`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-client-fetch-for-server-data`,title:`Client fetch for server data`,severity:`warn`,recommendation:`Remove 'use client' and fetch directly in the Server Component. No API round-trip, and secrets stay on the server.`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-client-side-redirect`,id:`nextjs-no-client-side-redirect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-client-side-redirect`,title:`Client-side redirect for navigation`,severity:`warn`,recommendation:`Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)`,recommendationOverride:`static-export-redirect`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-css-link`,id:`nextjs-no-css-link`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-css-link`,title:`Linked stylesheet bypasses Next.js CSS optimization`,severity:`warn`,recommendation:`Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-default-export-in-route-handler`,id:`nextjs-no-default-export-in-route-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-default-export-in-route-handler`,title:`Default export in route handler`,severity:`error`,recommendation:"Replace `export default` with named HTTP method exports because Next.js ignores default exports in `route.ts`.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-edge-og-runtime`,id:`nextjs-no-edge-og-runtime`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-edge-og-runtime`,title:`Edge runtime in OG image route`,severity:`warn`,recommendation:"Remove `export const runtime = 'edge'` from OG image files. The default Node.js runtime supports more fonts and APIs",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-font-link`,id:`nextjs-no-font-link`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-font-link`,title:`Google Fonts loaded via link`,severity:`warn`,recommendation:'`import { Inter } from "next/font/google"` for self-hosting, zero layout shift, and no render-blocking requests',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-google-analytics-script`,id:`nextjs-no-google-analytics-script`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-google-analytics-script`,title:`Manual Google Analytics script blocks optimized loading`,severity:`warn`,recommendation:"Use `import { GoogleAnalytics } from '@next/third-parties/google'` for automatic optimization and smaller bundles.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-head-import`,id:`nextjs-no-head-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-head-import`,title:`next/head in App Router`,severity:`error`,recommendation:"Use the Metadata API because `next/head` is ignored in the App Router and meta tags will not render.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-img-element`,id:`nextjs-no-img-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-img-element`,title:`Plain img ships unoptimized images`,severity:`warn`,recommendation:"Use `next/image` so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-native-script`,id:`nextjs-no-native-script`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-native-script`,title:`Plain script can block Next.js rendering`,severity:`warn`,recommendation:'Use `next/script` with `strategy="afterInteractive"` or `"lazyOnload"` so third-party scripts do not block rendering.',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-polyfill-script`,id:`nextjs-no-polyfill-script`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-polyfill-script`,title:`Redundant polyfill script`,severity:`warn`,recommendation:`Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-redirect-in-try-catch`,id:`nextjs-no-redirect-in-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-redirect-in-try-catch`,title:`redirect() inside try-catch`,severity:`warn`,recommendation:"Move `redirect()` or `notFound()` outside the try block, or rethrow in `catch`, because these APIs throw control-flow errors that catch blocks swallow.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-script-in-head`,id:`nextjs-no-script-in-head`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-script-in-head`,title:`next/script inside next/head`,severity:`error`,recommendation:"Move `<Script>` outside of `<Head>`. next/script manages its own placement and ignores head context",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-side-effect-in-get-handler`,id:`nextjs-no-side-effect-in-get-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-side-effect-in-get-handler`,title:`Side effect in GET handler`,severity:`error`,recommendation:`GET requests can be prefetched and are open to CSRF. Move the side effect to a POST handler.`,category:`Security`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-use-search-params-without-suspense`,id:`nextjs-no-use-search-params-without-suspense`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-use-search-params-without-suspense`,title:`useSearchParams without Suspense`,severity:`warn`,recommendation:"Wrap the component using `useSearchParams` in `<Suspense>` so the rest of the page can stay statically rendered.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-vercel-og-import`,id:`nextjs-no-vercel-og-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-vercel-og-import`,title:`@vercel/og import instead of next/og`,severity:`warn`,recommendation:'Use `import { ImageResponse } from "next/og"`; do not import `@vercel/og` directly because Next.js already bundles it.',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-access-key`,id:`no-access-key`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-access-key`,title:`accessKey attribute used`,severity:`warn`,recommendation:"Do not use `accessKey`. It conflicts with assistive tech shortcuts.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-adjust-state-on-prop-change`,id:`no-adjust-state-on-prop-change`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-adjust-state-on-prop-change`,title:`State adjusted after a prop changes`,severity:`warn`,recommendation:`Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-all-caps-body-text`,id:`no-all-caps-body-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-all-caps-body-text`,title:`Long body copy is set in all caps`,severity:`warn`,recommendation:`Use sentence case for paragraphs and reserve uppercase styling for short labels.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-arbitrary-px-font-size`,id:`no-arbitrary-px-font-size`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-arbitrary-px-font-size`,title:`Pixel arbitrary font size`,severity:`warn`,recommendation:"Use `rem` for arbitrary font sizes (`text-[0.8125rem]`, not `text-[13px]`) so text scales with the user's root font-size preference. Pixels stay fine for `border-*` / `outline-*`.",category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-aria-hidden-on-body`,id:`no-aria-hidden-on-body`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-aria-hidden-on-body`,title:`Document body hidden from assistive technology`,severity:`error`,recommendation:`Remove aria-hidden from the document body and hide only the intended subtree.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-aria-hidden-on-focusable`,id:`no-aria-hidden-on-focusable`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-aria-hidden-on-focusable`,title:`aria-hidden on focusable element`,severity:`warn`,recommendation:"Remove `aria-hidden` from focusable elements, or stop them being focusable, so keyboard users do not land on content screen readers hide.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-aria-invalid-without-description`,id:`no-aria-invalid-without-description`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-aria-invalid-without-description`,title:`Invalid control has no error description`,severity:`warn`,recommendation:`Connect an invalid control to its error text with aria-describedby or aria-errormessage.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-arithmetic-on-optional-chained-operand`,id:`no-arithmetic-on-optional-chained-operand`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-arithmetic-on-optional-chained-operand`,title:`Multiplicative math on optional-chained value can be NaN`,severity:`warn`,recommendation:"An optional chain is `undefined` when it short-circuits, so `*`/`/`/`%` on it produces `NaN`, which silently corrupts formatting and comparisons. Provide a `?? fallback` or guard the chain root before the arithmetic.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-array-find-result-member-access-without-guard`,id:`no-array-find-result-member-access-without-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-array-find-result-member-access-without-guard`,title:`Unguarded member access on find() result`,severity:`warn`,recommendation:"`Array.prototype.find`/`findLast` return `undefined` when no element matches, so guard the result with optional chaining (`?.`) or a null check before reading a property, indexing, or calling it.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-array-index-as-key`,id:`no-array-index-as-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-array-index-as-key`,title:`Array index used as a key`,severity:`warn`,recommendation:"Use a stable id from the item, like `key={item.id}` or `key={item.slug}`. Index keys break when the list reorders or filters.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-array-index-deref-without-bounds-or-empty-guard`,id:`no-array-index-deref-without-bounds-or-empty-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-array-index-deref-without-bounds-or-empty-guard`,title:`Array index result dereferenced without a guard`,severity:`warn`,recommendation:"An array index read is typed `T` but is `T | undefined` at runtime, so dereferencing it on an empty list, a non-matching regex, or a short split throws. Add a length/emptiness check or optional chaining before the access.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-array-index-key`,id:`no-array-index-key`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-array-index-key`,title:`Array index used as a key`,severity:`warn`,recommendation:"Use a stable `key` from your data so reordered items keep the right state and DOM.",category:`Performance`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-assertive-status`,id:`no-assertive-status`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-assertive-status`,title:`Status live region interrupts assistive technology`,severity:`warn`,recommendation:'Keep `role="status"` polite. Use `role="alert"` only when an interruption is genuinely imperative.',category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-async-effect-callback`,id:`no-async-effect-callback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-async-effect-callback`,title:`Async effect callback`,severity:`warn`,recommendation:"Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-async-event-handler-without-reentry-guard`,id:`no-async-event-handler-without-reentry-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-async-event-handler-without-reentry-guard`,title:`Async mutating handler without re-entry guard`,severity:`warn`,recommendation:"An async onClick/onSubmit handler on a host control that awaits a mutating request and sets state only afterward stays interactive across the await, so a double-click fires the write twice. Add a leading `if (busy) return` guard, or set a flag before the await inside `try` and reset it in `finally` while the control is disabled.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-auto-scrolling-content`,id:`no-auto-scrolling-content`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-auto-scrolling-content`,title:`Content auto-scrolls forever`,severity:`warn`,recommendation:`Keep content still so readers control the pace, or provide an accessible pause control for essential live movement.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-autofocus`,id:`no-autofocus`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-autofocus`,title:`Autofocus on an element`,severity:`warn`,recommendation:"Do not use `autoFocus`. It disorients users on load.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-autoplay-without-muted`,id:`no-autoplay-without-muted`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-autoplay-without-muted`,title:`Autoplaying media without muted`,severity:`warn`,recommendation:"Always pair `autoPlay` with `muted` (and `playsInline`): `<video autoPlay muted loop playsInline />`. If the sound matters, drop `autoPlay` and let users start it.",category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-barrel-import`,id:`no-barrel-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-barrel-import`,title:`Import from a barrel file`,severity:`warn`,recommendation:"Import from the direct path: `import { Button } from './components/Button'` instead of `./components`",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-blocked-paste`,id:`no-blocked-paste`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-blocked-paste`,title:`Paste blocked in an authentication field`,severity:`error`,recommendation:`Allow paste so people can use password managers, verification codes, assistive tools, and copied text without retyping it.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-boolean-toggle-without-functional-update`,id:`no-boolean-toggle-without-functional-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-boolean-toggle-without-functional-update`,title:`Boolean toggle reads a stale value`,severity:`warn`,recommendation:"Toggle boolean state with the functional updater `setX(previous => !previous)` so deferred callbacks always read the latest committed value.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-broken-image-source`,id:`no-broken-image-source`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-broken-image-source`,title:`Image has no usable source`,severity:`warn`,recommendation:`Provide a real image URL or remove the image element until an asset is available.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-call-component-as-function`,id:`no-call-component-as-function`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-call-component-as-function`,title:`Component called as a function`,severity:`warn`,recommendation:"Render components as JSX (`<Component />`), never call them like functions (`Component(props)`). A direct call runs the component outside React and breaks hooks, state, and memoization.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-cascading-set-state`,id:`no-cascading-set-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-cascading-set-state`,title:`Multiple setState calls in one effect`,severity:`warn`,recommendation:`Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-chain-state-updates`,id:`no-chain-state-updates`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-chain-state-updates`,title:`State updates chained through effects`,severity:`warn`,recommendation:`Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations`,category:`Bugs`,framework:`global`,requires:[`react`],disabledWhen:[`react:18`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-children-prop`,id:`no-children-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-children-prop`,title:`Children passed as a prop`,severity:`warn`,recommendation:`Nest children between the tags so the rendered content is visible in JSX and cannot be hidden inside a props object.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-clipped-overlay`,id:`no-clipped-overlay`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-clipped-overlay`,title:`Overlay can be clipped by an overflow container`,severity:`warn`,recommendation:`Render the overlay outside the clipping container, usually through a portal, or remove the clipping overflow.`,category:`Bugs`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-clone-element`,id:`no-clone-element`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-clone-element`,title:`cloneElement makes child props fragile`,severity:`warn`,recommendation:`Pass children or render props instead so parent code does not depend on fragile cloned child props.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-collapse-request-error-to-empty-state`,id:`no-collapse-request-error-to-empty-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-collapse-request-error-to-empty-state`,title:`Request failure rendered as an empty result`,severity:`warn`,recommendation:`Keep request failure separate from an empty successful response, render an error with a retry action, and leave the previous result visible when appropriate.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-collapsed-literal-or-chain-as-value`,id:`no-collapsed-literal-or-chain-as-value`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-collapsed-literal-or-chain-as-value`,title:`All-literal logical chain used as a multi-value expression`,severity:`warn`,recommendation:"Compare against each value separately (or use an array `.includes(x)`) instead of an all-literal `||`/`&&` chain, which short-circuits to its first literal and drops the rest.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-common-root-font`,id:`no-common-root-font`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-common-root-font`,title:`Page root uses a generic default font choice`,severity:`warn`,recommendation:`Choose a typeface that supports the product's voice, or document why the conventional UI font is intentional.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-conflicting-spring-options`,id:`no-conflicting-spring-options`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-conflicting-spring-options`,title:`Motion spring mixes incompatible option modes`,severity:`warn`,recommendation:`Choose either stiffness/damping/mass or duration/bounce for a Motion spring so every configured value takes effect.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-controlled-input-value-without-state-update`,id:`no-controlled-input-value-without-state-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-controlled-input-value-without-state-update`,title:`Controlled input value is a fixed literal`,severity:`warn`,recommendation:"Drive the input's `value` from state (`const [value, setValue] = useState(...)`) that `onChange` updates, or drop `value` if the field is meant to be read-only.",category:`Bugs`,framework:`global`,tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-controlled-selection-focus-effect`,id:`no-controlled-selection-focus-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-controlled-selection-focus-effect`,title:`Controlled selection change steals focus`,severity:`warn`,recommendation:`Move selection-driven focus into the keyboard or pointer navigation path, or prove that the list just opened before focusing the selected item.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-cramped-container-padding`,id:`no-cramped-container-padding`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-cramped-container-padding`,title:`Bounded text container has cramped padding`,severity:`warn`,recommendation:`Give text at least 8px of space inside a visible border or colored surface.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-create-context-in-render`,id:`no-create-context-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-context-in-render`,title:`createContext called during render`,severity:`error`,recommendation:"Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-create-object-url-in-render`,id:`no-create-object-url-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-object-url-in-render`,title:`Object URL is created during render`,severity:`warn`,recommendation:`Create object URLs in an effect or event handler and revoke each URL when it is replaced or no longer needed.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-create-object-url-without-revoke`,id:`no-create-object-url-without-revoke`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-object-url-without-revoke`,title:`createObjectURL without revokeObjectURL`,severity:`warn`,recommendation:"Call `URL.revokeObjectURL(url)` once the object URL is no longer needed (after the download, in a `useEffect` cleanup, or on unmount). An object URL keeps its Blob/File alive for the document lifetime until it is revoked.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-create-ref-in-function-component`,id:`no-create-ref-in-function-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-ref-in-function-component`,title:`createRef in function component`,severity:`warn`,recommendation:"Replace `createRef()` with the `useRef()` hook inside function components and hooks. `createRef` is only for class components.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-create-store-in-render`,id:`no-create-store-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-store-in-render`,title:`Store created during render`,severity:`error`,recommendation:`Create stores at module scope so subscribers are not cut off and saved state does not reset every render.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-crushed-letter-spacing`,id:`no-crushed-letter-spacing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-crushed-letter-spacing`,title:`Letter spacing compresses text excessively`,severity:`warn`,recommendation:`Loosen the tracking until each character keeps a distinct, readable shape.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-danger`,id:`no-danger`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-danger`,title:`Raw HTML injection can run unsafe markup`,severity:`warn`,recommendation:`Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.`,category:`Security`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-danger-with-children`,id:`no-danger-with-children`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-danger-with-children`,title:`dangerouslySetInnerHTML with children`,severity:`error`,recommendation:"Use either `children` or `dangerouslySetInnerHTML` so React does not ignore one source of content.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-dark-mode-glow`,id:`no-dark-mode-glow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-dark-mode-glow`,title:`Colored glow on dark background`,severity:`warn`,recommendation:"Use a subtle `box-shadow` in neutral colors for depth, or a faint `border`. Colored glows on dark backgrounds look overdone.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-blur-orb`,id:`no-decorative-blur-orb`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-blur-orb`,title:`Empty blurred color orb decorates the layout`,severity:`warn`,recommendation:`Use structure, imagery, or a restrained surface treatment instead of an oversized blurred color blob.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-grid-background`,id:`no-decorative-grid-background`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-grid-background`,title:`Surface draws a decorative grid background`,severity:`warn`,recommendation:`Reserve coordinate grids for data or spatial interfaces; use a quieter surface for decoration.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-pulse`,id:`no-decorative-pulse`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-pulse`,title:`Stable copy pulses for attention`,severity:`warn`,recommendation:`Reserve pulsing motion for real in-progress feedback. Remove fake blinking cursors and use static hierarchy for decorative emphasis.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-radial-spotlight`,id:`no-decorative-radial-spotlight`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-radial-spotlight`,title:`Large decorative radial spotlight`,severity:`warn`,recommendation:`Use product-specific imagery, structure, or a restrained solid surface instead of a large translucent radial glow.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-default-props`,id:`no-default-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-default-props`,title:`defaultProps removed in React 19`,severity:`warn`,recommendation:'React 19 drops `Component.defaultProps` for function components. Set the defaults in the destructured props instead: `function Foo({ size = "md", variant = "primary" })` instead of `Foo.defaultProps = { size: "md", variant: "primary" }`.',category:`Maintainability`,framework:`global`,requires:[`react:19`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-default-purple-page-gradient`,id:`no-default-purple-page-gradient`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-default-purple-page-gradient`,title:`Page root uses a default purple-spectrum gradient`,severity:`warn`,recommendation:`Build the page palette from the product's identity instead of a conventional purple-to-bright gradient.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-default-warm-page-surface`,id:`no-default-warm-page-surface`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-default-warm-page-surface`,title:`Page defaults to a warm off-white surface`,severity:`warn`,recommendation:`Choose the page surface from an intentional product palette rather than a generic warm neutral default.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-deprecated-keyboard-event-keycode-which`,id:`no-deprecated-keyboard-event-keycode-which`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-deprecated-keyboard-event-keycode-which`,title:`Deprecated KeyboardEvent keyCode or which`,severity:`warn`,recommendation:"`KeyboardEvent.keyCode`/`which`/`charCode` are deprecated and layout/engine dependent for character keys. Branch on `event.key` (logical key like `'/'`) or `event.code` (physical position) so the handler works across keyboard layouts and browsers.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-deprecated-tailwind-class`,id:`no-deprecated-tailwind-class`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-deprecated-tailwind-class`,title:`Deprecated Tailwind v4 utility`,severity:`warn`,recommendation:"Tailwind v4 renamed these utilities: `bg-gradient-*` → `bg-linear-*`, `flex-shrink-*` → `shrink-*`, `flex-grow-*` → `grow-*`, `overflow-ellipsis` → `text-ellipsis`. Use the new names.",category:`Maintainability`,framework:`global`,requires:[`tailwind:4`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-derived-state`,id:`no-derived-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-derived-state`,title:`Derived value copied into state`,severity:`warn`,recommendation:`Work out the value while rendering (or with useMemo if it's expensive) instead of copying it into state and synchronizing it during render or through an effect. See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-derived-state-effect`,id:`no-derived-state-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-derived-state-effect`,title:`Derived state stored in an effect`,severity:`warn`,recommendation:"Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-derived-useState`,id:`no-derived-useState`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-derived-useState`,title:`Prop derived into useState`,severity:`warn`,recommendation:"Compute the value inline so prop changes do not leave `useState` holding a stale copy.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-did-mount-set-state`,id:`no-did-mount-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-did-mount-set-state`,title:`setState in componentDidMount`,severity:`warn`,recommendation:"Setting state in `componentDidMount` triggers an extra render. Use `getDerivedStateFromProps` or initial state instead.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-did-update-set-state`,id:`no-did-update-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-did-update-set-state`,title:`setState in componentDidUpdate`,severity:`warn`,recommendation:"Setting state in `componentDidUpdate` causes another render and can loop. Use `getDerivedStateFromProps` instead.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-direct-mutation-state`,id:`no-direct-mutation-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-direct-mutation-state`,title:`Direct mutation of this.state`,severity:`error`,recommendation:"Don't change `this.state` by hand. `setState()` overwrites it anyway, so always go through `setState()`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-direct-state-mutation`,id:`no-direct-state-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-direct-state-mutation`,title:`State mutated in place`,severity:`warn`,recommendation:"Call the setter with a brand new value instead: `setItems([...items, newItem])`, `setItems(items.filter(x => x !== target))`, or `setItems(items.toSorted(...))`. React only redraws when the value is new, so changing it in place does nothing.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-disabled-zoom`,id:`no-disabled-zoom`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-disabled-zoom`,title:`Zoom disabled on viewport`,severity:`error`,recommendation:"Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-distracting-elements`,id:`no-distracting-elements`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-distracting-elements`,title:`Distracting marquee or blink element`,severity:`error`,recommendation:"Replace `<marquee>` and `<blink>` with normal markup so motion does not distract or disorient users.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-document-start-view-transition`,id:`no-document-start-view-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-document-start-view-transition`,title:`Direct document.startViewTransition call`,severity:`warn`,recommendation:`Render a <ViewTransition> component and update inside startTransition or useDeferredValue, and React calls startViewTransition for you.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-document-write`,id:`no-document-write`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-document-write`,title:`document.write/writeln`,severity:`warn`,recommendation:"Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",category:`Performance`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-duplicate-static-id-reference`,id:`no-duplicate-static-id-reference`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-duplicate-static-id-reference`,title:`Referenced ID is duplicated`,severity:`error`,recommendation:`Give every statically referenced element a unique id within the rendered JSX tree.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-dynamic-import-path`,id:`no-dynamic-import-path`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-dynamic-import-path`,title:`Non-static dynamic import path`,severity:`warn`,recommendation:"Use a plain string path: `import('./feature/heavy.js')` so the bundler can split this into its own chunk.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-dynamic-tailwind-class-fragment`,id:`no-dynamic-tailwind-class-fragment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-dynamic-tailwind-class-fragment`,title:`Tailwind utility is assembled dynamically`,severity:`warn`,recommendation:`Use complete, statically discoverable Tailwind utility strings for each state instead of interpolating part of a class token.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-eager-new-in-use-state-initializer`,id:`no-eager-new-in-use-state-initializer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-eager-new-in-use-state-initializer`,title:`Eager constructor in useState initializer`,severity:`warn`,recommendation:"Wrap nontrivial construction in a function (`useState(() => new X())`) so it only runs on the first render.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-ease-in-motion`,id:`no-ease-in-motion`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ease-in-motion`,title:`UI motion starts with ease-in`,severity:`warn`,recommendation:`Use ease-out for entrances and exits, or ease-in-out when an element remains visible while moving.`,category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-effect-chain`,id:`no-effect-chain`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-chain`,title:`Effects chained together`,severity:`warn`,recommendation:"Compute as much as possible during render (e.g. `const isGameOver = round > 5`) and write all related state inside the event handler that originally fires the chain. Each effect link adds an extra render and makes the code rigid as requirements evolve",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-effect-event-handler`,id:`no-effect-event-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-event-handler`,title:`Effect used as an event handler`,severity:`warn`,recommendation:`Move event logic into the handler that starts it so the side effect does not run late after an extra render.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-effect-event-in-deps`,id:`no-effect-event-in-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-event-in-deps`,title:`Effect Event listed in effect deps`,severity:`error`,recommendation:`Call the useEffectEvent function inside the effect body and don't list it in the deps. It's non-reactive on purpose, so it must be omitted.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-effect-with-fresh-deps`,id:`no-effect-with-fresh-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-with-fresh-deps`,title:`Effect dependency recreated every render`,severity:`error`,recommendation:`Move the value inside the hook body and depend on its simple inputs instead, or wrap it in useMemo / useCallback so it stays the same between renders.`,category:`Bugs`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],isScanRule:!1}},{key:`react-doctor/no-effect-wrapper-discards-callback-cleanup-return`,id:`no-effect-wrapper-discards-callback-cleanup-return`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-wrapper-discards-callback-cleanup-return`,title:`Effect wrapper discards forwarded cleanup return`,severity:`warn`,recommendation:"A custom effect wrapper must return its forwarded EffectCallback's result so React can run the cleanup. Calling it as a bare `fn()` instead of `return fn()` silently drops the cleanup, leaking every subscription/timer/listener it set up.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-emoji-heading-decoration`,id:`no-emoji-heading-decoration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-emoji-heading-decoration`,title:`Heading uses emoji as decoration`,severity:`warn`,recommendation:`Use purposeful product artwork or a consistent icon system outside the heading instead of decorating heading copy with platform-dependent emoji.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-empty-card-shell`,id:`no-empty-card-shell`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-empty-card-shell`,title:`Empty semantic element renders as a card`,severity:`warn`,recommendation:`Render meaningful content or remove the bordered, rounded shell until content exists.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-enter-submit-without-ime-composition-guard`,id:`no-enter-submit-without-ime-composition-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-enter-submit-without-ime-composition-guard`,title:`Enter submit without IME composition guard`,severity:`warn`,recommendation:"Bail on IME composition before acting on Enter: `if (e.nativeEvent.isComposing) return;` (or track composition with `onCompositionStart`/`onCompositionEnd`). Otherwise Enter fires mid-composition and commits a half-typed value for CJK users.",category:`Bugs`,framework:`global`,requires:[`i18n`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-eval`,id:`no-eval`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-eval`,title:`eval() runs untrusted code strings`,severity:`error`,recommendation:"Use `JSON.parse` for data, or rewrite the code so it doesn't build and run code from strings.",category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-event-handler`,id:`no-event-handler`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-event-handler`,title:`Event logic handled in an effect`,severity:`warn`,recommendation:`Run the side effect in the event handler that triggers it, instead of watching its state from a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-event-trigger-state`,id:`no-event-trigger-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-event-trigger-state`,title:`State exists only to trigger an effect`,severity:`warn`,recommendation:"Delete the trigger state (`useState(null)` plus the `useEffect` that watches it) and call the side effect like `post(...)`, `navigate(...)`, or `track(...)` directly in the event handler that set it. State shouldn't exist just to kick off an effect.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-excessive-card-surfaces`,id:`no-excessive-card-surfaces`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-card-surfaces`,title:`Page boxes too many groups into cards`,severity:`warn`,recommendation:`Let related content share the page surface and use spacing, dividers, or typography before adding another card boundary.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-centered-copy`,id:`no-excessive-centered-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-centered-copy`,title:`Page centers multiple body-copy blocks`,severity:`warn`,recommendation:`Keep longer body copy left-aligned and reserve centered text for one concise focal statement.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-font-families`,id:`no-excessive-font-families`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-font-families`,title:`Page mixes too many font families`,severity:`warn`,recommendation:`Use a display face, a body face, and at most one purposeful outlier such as monospace.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-motion-stagger`,id:`no-excessive-motion-stagger`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-motion-stagger`,title:`Motion child stagger is too long`,severity:`warn`,recommendation:`Keep per-item stagger intervals short so later content does not feel artificially delayed.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-pill-treatment`,id:`no-excessive-pill-treatment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-pill-treatment`,title:`Page overuses pill-shaped treatments`,severity:`warn`,recommendation:`Use pills only for controls or compact metadata whose shape communicates a real role.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-fake-browser-chrome`,id:`no-fake-browser-chrome`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fake-browser-chrome`,title:`Preview redraws decorative browser chrome`,severity:`warn`,recommendation:`Show the product screenshot directly or use a purposeful frame instead of adding imitation browser controls.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-fetch-in-effect`,id:`no-fetch-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fetch-in-effect`,title:`Data fetching inside an effect`,severity:`warn`,recommendation:"Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-fetch-response-used-without-status-check`,id:`no-fetch-response-used-without-status-check`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fetch-response-used-without-status-check`,title:`fetch Response consumed without status check`,severity:`warn`,recommendation:"Check `response.ok` (or `response.status`) before consuming a `fetch` Response with `.json()`/`.text()`/`.blob()`. `fetch` resolves on HTTP 4xx/5xx, so an unchecked response parses the error body as success or crashes on an always-truthy guard.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-fill-map-element-as-key`,id:`no-fill-map-element-as-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fill-map-element-as-key`,title:`fill().map() first param is the element, not the index`,severity:`warn`,recommendation:"After `.fill(value)` every element is identical, so a lone `.map((n) => ...)` binds `n` to that value (whatever the parameter is named) and gives every child the same key. Add the index as the second parameter: `.map((_, index) => ...)`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-find-dom-node`,id:`no-find-dom-node`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-find-dom-node`,title:`findDOMNode breaks component encapsulation`,severity:`warn`,recommendation:"Use a ref to reach DOM nodes because `findDOMNode` was removed in React 19 and can crash the app.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-fixed-inside-transformed-ancestor`,id:`no-fixed-inside-transformed-ancestor`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fixed-inside-transformed-ancestor`,title:`Fixed element is scoped by a transformed ancestor`,severity:`warn`,recommendation:`Move viewport-fixed UI outside transformed ancestors, or intentionally use absolute positioning within that containing block.`,category:`Bugs`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-flat-page-type-scale`,id:`no-flat-page-type-scale`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-flat-page-type-scale`,title:`Page typography uses a compressed size range`,severity:`warn`,recommendation:`Use a clearer size hierarchy when a page declares several explicit typography steps.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-floating-then-in-jsx-handler`,id:`no-floating-then-in-jsx-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-floating-then-in-jsx-handler`,title:`Floating .then in a JSX event handler`,severity:`warn`,recommendation:"A `.then()` chain with no `.catch` in an event handler becomes an uncaught promise rejection no error boundary can catch; add a `.catch` handler (or make the handler `async` and `try/catch`).",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-flush-sync`,id:`no-flush-sync`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-flush-sync`,title:`flushSync skips View Transitions`,severity:`warn`,recommendation:`flushSync forces an immediate update that skips View Transitions and concurrent rendering. Use startTransition for updates that are not urgent.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-focus-in-animation-completion-handler`,id:`no-focus-in-animation-completion-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-focus-in-animation-completion-handler`,title:`Focus waits for animation completion`,severity:`warn`,recommendation:`Move focus when the interaction state changes, independently of visual animation completion, so canceled or reduced animations cannot delay or suppress keyboard focus.`,category:`Accessibility`,framework:`global`,tags:[`design`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-focusable-content-in-aria-hidden`,id:`no-focusable-content-in-aria-hidden`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-focusable-content-in-aria-hidden`,title:`aria-hidden subtree contains focusable content`,severity:`warn`,recommendation:`Remove focusable descendants from aria-hidden content, or hide and disable the whole subtree together.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-focusable-content-in-role-text`,id:`no-focusable-content-in-role-text`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-focusable-content-in-role-text`,title:`Focusable control inside role=text`,severity:`warn`,recommendation:`Move interactive descendants outside role=text, or remove role=text so their semantics remain available.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-full-lodash-import`,id:`no-full-lodash-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-full-lodash-import`,title:`Full lodash import`,severity:`warn`,recommendation:"Import just the function you need: `import debounce from 'lodash/debounce'`. Saves about 70kb.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-full-viewport-centered-hero`,id:`no-full-viewport-centered-hero`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-full-viewport-centered-hero`,title:`Hero uses a full-viewport centered template`,severity:`warn`,recommendation:`Give the opening section a content-driven height and a composition tied to the product instead of centering a headline in an empty viewport.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-full-viewport-width`,id:`no-full-viewport-width`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-full-viewport-width`,title:`Full viewport width causes overflow`,severity:`warn`,recommendation:"Prefer `w-full` (`width: 100%`) over `w-screen` / `100vw`. `100vw` ignores the scrollbar gutter and overflows horizontally.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-generic-handler-names`,id:`no-generic-handler-names`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-generic-handler-names`,title:`Vague event handler name`,severity:`warn`,recommendation:"Rename it to say what it does. For example `handleSubmit` could be `saveUserProfile`, and `handleClick` could be `toggleSidebar`.",category:`Maintainability`,framework:`global`,tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-generic-marketing-copy`,id:`no-generic-marketing-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-generic-marketing-copy`,title:`Page uses generic marketing language`,severity:`warn`,recommendation:`Replace broad promotional phrases with concrete capabilities, outcomes, or evidence.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-generic-purple-blue-icon-gradient`,id:`no-generic-purple-blue-icon-gradient`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-generic-purple-blue-icon-gradient`,title:`Compact icon tile uses a generic purple-to-blue gradient`,severity:`warn`,recommendation:`Use a product color, neutral surface, or unboxed icon instead of a generic purple-to-blue gradient tile.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-giant-component`,id:`no-giant-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-giant-component`,title:`Large component is hard to read and change`,severity:`warn`,recommendation:`Pull each section into its own component so the parent is easier to read, test, and change.`,category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-global-css-variable-animation`,id:`no-global-css-variable-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-global-css-variable-animation`,title:`Animating a global CSS variable`,severity:`error`,recommendation:"Set the variable on the element that needs it instead of a parent, or use `@property` with `inherits: false`. Better yet, update `element.style.transform` directly",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-gradient-text`,id:`no-gradient-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-gradient-text`,title:`Gradient text is hard to read`,severity:`warn`,recommendation:`Use a solid text color so it stays readable. For emphasis, change the weight, size, or color instead of using a gradient.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-gray-on-colored-background`,id:`no-gray-on-colored-background`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-gray-on-colored-background`,title:`Gray text on colored background`,severity:`warn`,recommendation:`Use white or near-white text, or a darker shade of the background color. Gray text on colored backgrounds looks washed out.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hairline-border-wide-shadow`,id:`no-hairline-border-wide-shadow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hairline-border-wide-shadow`,title:`Hairline border is paired with a diffuse shadow`,severity:`warn`,recommendation:`Choose either a defined edge or soft elevation instead of emphasizing both.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hero-eyebrow-chip`,id:`no-hero-eyebrow-chip`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hero-eyebrow-chip`,title:`Hero uses a decorative eyebrow label`,severity:`warn`,recommendation:`Integrate the context into the headline or navigation instead of adding a generic chip above the hero title.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hover-only-reveal`,id:`no-hover-only-reveal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hover-only-reveal`,title:`Content is revealed only on hover`,severity:`warn`,recommendation:`Mirror hover reveals with focus or focus-within, and keep essential controls available to touch users.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hydration-branch-on-browser-global`,id:`no-hydration-branch-on-browser-global`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hydration-branch-on-browser-global`,title:`Server and client render different branches`,severity:`error`,recommendation:`Render the same initial output on the server and client, then switch after mount or use useSyncExternalStore with a stable server snapshot.`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-icon-tile-heading-stack`,id:`no-icon-tile-heading-stack`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-icon-tile-heading-stack`,title:`Card stacks an icon tile above its heading`,severity:`warn`,recommendation:`Place the icon beside the heading or let it sit in the content flow without another boxed surface.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-image-hover-transform`,id:`no-image-hover-transform`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-image-hover-transform`,title:`Image scales or rotates on hover`,severity:`warn`,recommendation:`Keep the image stable, or use a subtler hover response tied to an actual interaction affordance.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-img-lazy-with-high-fetchpriority`,id:`no-img-lazy-with-high-fetchpriority`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-img-lazy-with-high-fetchpriority`,title:`Lazy image with high fetchPriority`,severity:`warn`,recommendation:'Don\'t combine `loading="lazy"` with `fetchPriority="high"`. A high-priority image (usually the LCP) should load eagerly; a lazy image is by definition not high priority.',category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-img-without-dimensions`,id:`no-img-without-dimensions`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-img-without-dimensions`,title:`Image has no reserved layout space`,severity:`warn`,recommendation:`Add width and height attributes, or reserve the image's aspect ratio with an explicit CSS box before it loads.`,category:`Performance`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-impure-call-at-module-scope`,id:`no-impure-call-at-module-scope`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-impure-call-at-module-scope`,title:`Nondeterministic built-in at module scope`,severity:`warn`,recommendation:"`Math.random()`, `Date.now()`, `performance.now()`, and `new Date()` run once at module load, so the value is frozen for the whole server process. Move the call into a function/component so it evaluates per request.",category:`Bugs`,framework:`global`,requires:[`ssr`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-impure-state-updater`,id:`no-impure-state-updater`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-impure-state-updater`,title:`State updater has side effects`,severity:`error`,recommendation:`Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-indeterminate-attribute`,id:`no-indeterminate-attribute`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-indeterminate-attribute`,title:`Indeterminate checkbox state set as an attribute`,severity:`warn`,recommendation:"Assign the checkbox element's `indeterminate` DOM property, usually through a ref, because the HTML attribute does not control its visual state.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-inert-pointer-affordance`,id:`no-inert-pointer-affordance`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inert-pointer-affordance`,title:`Pointer cursor has no interaction`,severity:`warn`,recommendation:`Remove the pointer cursor or put the affordance on the control that actually handles the interaction.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-inert-sticky-position`,id:`no-inert-sticky-position`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inert-sticky-position`,title:`Sticky positioning has no inset`,severity:`warn`,recommendation:`Set a non-auto inset such as top, bottom, inset-block-start, or its matching Tailwind utility on the sticky axis.`,category:`Bugs`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-initialize-state`,id:`no-initialize-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-initialize-state`,title:`State initialized from a mount effect`,severity:`warn`,recommendation:`Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-inline-bounce-easing`,id:`no-inline-bounce-easing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-bounce-easing`,title:`Bouncy easing animation`,severity:`warn`,recommendation:"Use `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo) for a natural finish. Real objects don't bounce.",category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-inline-exhaustive-style`,id:`no-inline-exhaustive-style`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-exhaustive-style`,title:`Large inline style object rebuilds every render`,severity:`warn`,recommendation:`Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-inline-hoc-on-component`,id:`no-inline-hoc-on-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-hoc-on-component`,title:`Function component defined inline inside an HOC call`,severity:`warn`,recommendation:"Extract the inline function into a named base component at module scope and pass the reference to the HOC (`const CardBase = (props) => ...; const Card = withTracking(CardBase);`). This restores rules-of-hooks and exhaustive-deps analysis and gives the component a stable display name.",category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-inline-prop-on-memo-component`,id:`no-inline-prop-on-memo-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-prop-on-memo-component`,title:`Inline prop defeats memo()`,severity:`warn`,recommendation:"Move the inline `() => ...` / `[]` / `{}` to a stable value with useMemo, useCallback, or module scope, so the memoized child stops redrawing on every parent render",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-interactive-element-to-noninteractive-role`,id:`no-interactive-element-to-noninteractive-role`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-interactive-element-to-noninteractive-role`,title:`Interactive element given noninteractive role`,severity:`warn`,recommendation:`Do not give an interactive element a role that says it is not interactive.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-invalid-progress-range`,id:`no-invalid-progress-range`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-invalid-progress-range`,title:`Progress value falls outside its range`,severity:`error`,recommendation:`Keep determinate progress values within a valid positive range so visual and assistive feedback report the same advancement.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-invisible-focus-control`,id:`no-invisible-focus-control`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-invisible-focus-control`,title:`Invisible native control lacks keyboard focus treatment`,severity:`warn`,recommendation:`Reveal the native control on focus or add a visible focus-within ring to the proxy surface around it.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-is-mounted`,id:`no-is-mounted`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-is-mounted`,title:`isMounted lets async callbacks update after unmount`,severity:`warn`,recommendation:"`isMounted` doesn't work in modern React. Track mount state with a ref, or cancel the async work instead.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-italic-serif-display-heading`,id:`no-italic-serif-display-heading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-italic-serif-display-heading`,title:`Display heading combines italic serif styling`,severity:`warn`,recommendation:`Use a roman display face or reserve the italic serif treatment for a smaller editorial accent.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-json-parse-stringify-clone`,id:`no-json-parse-stringify-clone`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-json-parse-stringify-clone`,title:`JSON parse/stringify deep clone`,severity:`warn`,recommendation:"Replace `JSON.parse(JSON.stringify(value))` with `structuredClone(value)`. It is faster and preserves Dates, Maps, Sets, and cyclic references.",category:`Performance`,framework:`global`,disabledWhen:[`react-native`],isScanRule:!1}},{key:`react-doctor/no-jsx-element-type`,id:`no-jsx-element-type`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-jsx-element-type`,title:`No JSX.Element`,severity:`warn`,recommendation:"Replace `JSX.Element` with `React.ReactNode`. `JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-justified-text`,id:`no-justified-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-justified-text`,title:`Justified text without hyphens`,severity:`warn`,recommendation:"Use `text-align: left` for body text. If you must justify, add `hyphens: auto` and `overflow-wrap: break-word`.",category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-large-animated-blur`,id:`no-large-animated-blur`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-large-animated-blur`,title:`Large animated blur`,severity:`warn`,recommendation:`Keep the blur under 10px, or blur a smaller element. Big blurs use a lot more GPU memory as the element grows`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-layout-property-animation`,id:`no-layout-property-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-layout-property-animation`,title:`Animating a layout property`,severity:`error`,recommendation:"Use `transform: translateX()` or `scale()` instead. They animate smoothly without making the browser redo layout or repaint",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-layout-shifting-interaction-state`,id:`no-layout-shifting-interaction-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-layout-shifting-interaction-state`,title:`Interaction state changes layout geometry`,severity:`warn`,recommendation:`Keep hover, focus, and pressed feedback to paint-only or transform properties so nearby content does not move when the state changes.`,category:`Maintainability`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-layout-transition-inline`,id:`no-layout-transition-inline`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-layout-transition-inline`,title:`Animating layout properties`,severity:`warn`,recommendation:"Animate `transform` and `opacity` instead, since they're cheap for the browser. For height, animate `grid-template-rows` from `0fr` to `1fr`.",category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-legacy-class-lifecycles`,id:`no-legacy-class-lifecycles`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-legacy-class-lifecycles`,title:`Legacy class lifecycle methods`,severity:`error`,recommendation:"Move `componentWillMount` work to `componentDidMount`, `componentWillReceiveProps` to `componentDidUpdate` or the static `getDerivedStateFromProps`, and `componentWillUpdate` to `getSnapshotBeforeUpdate` plus `componentDidUpdate`. The `UNSAFE_` prefix only hides the warning. React 19 removes both.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-legacy-context-api`,id:`no-legacy-context-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-legacy-context-api`,title:`Legacy context API`,severity:`error`,recommendation:"Swap `childContextTypes` + `getChildContext` for `const MyContext = createContext(...)` and `<MyContext.Provider value={...}>`. Swap `contextTypes` for `static contextType = MyContext` or `useContext()` in a function component. Move the provider and every consumer together, or some consumers read the wrong context.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-loading-flag-reset-outside-finally`,id:`no-loading-flag-reset-outside-finally`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-loading-flag-reset-outside-finally`,title:`Loading flag reset outside finally`,severity:`warn`,recommendation:"A trailing `setLoading(false)` after an `await` never runs if the awaited call rejects, so the flag stays stuck truthy; reset it in a `finally` block (or mirror the reset on every catch) so it clears on both paths.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-locale-format-in-render`,id:`no-locale-format-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-locale-format-in-render`,title:`Locale/timezone formatting during render`,severity:`warn`,recommendation:`Format locale/timezone-dependent values in a post-mount useEffect + state, or pass an explicit locale and timeZone so the server and the browser render the same text. Only runs on SSR-capable projects.`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-long-transition-duration`,id:`no-long-transition-duration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-long-transition-duration`,title:`Transition duration too long`,severity:`warn`,recommendation:`Keep UI transitions under 1s. Use about 100 to 150ms for instant feedback, 200 to 300ms for state changes, and 300 to 500ms for layout. Save longer ones for big page-load animations.`,category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-low-contrast-inline-style`,id:`no-low-contrast-inline-style`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-low-contrast-inline-style`,title:`Low-contrast text in inline style`,severity:`warn`,recommendation:`Text needs a WCAG contrast ratio of at least 4.5:1 (3:1 for large/bold text) against its background. Darken or lighten one of the colors until it passes.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-manufactured-contrast-copy`,id:`no-manufactured-contrast-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-manufactured-contrast-copy`,title:`Page repeatedly uses manufactured contrast copy`,severity:`warn`,recommendation:`State the value directly instead of repeatedly contrasting it with a vague alternative.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-many-boolean-props`,id:`no-many-boolean-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-many-boolean-props`,title:`Boolean prop combinations are hard to test`,severity:`warn`,recommendation:`Split boolean-heavy APIs into smaller components or named variants so combinations stay testable.`,category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-match-media-in-state-initializer`,id:`no-match-media-in-state-initializer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-match-media-in-state-initializer`,title:`matchMedia in state initializer`,severity:`warn`,recommendation:"Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-mirror-prop-effect`,id:`no-mirror-prop-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mirror-prop-effect`,title:`Prop mirrored into state via effect`,severity:`warn`,recommendation:"Delete both the `useState` and the `useEffect` and read the prop directly while rendering. Copying a prop into state shows the old value on the first render before the effect catches up.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-mixed-animation-owners`,id:`no-mixed-animation-owners`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mixed-animation-owners`,title:`CSS and Motion animate the same property`,severity:`warn`,recommendation:`Give each property one animation owner. Remove its CSS transition when Motion controls it, or move the CSS transition to a different element or property.`,category:`Maintainability`,framework:`global`,tags:[`design`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-mixed-icon-libraries`,id:`no-mixed-icon-libraries`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mixed-icon-libraries`,title:`File mixes visual icon families`,severity:`warn`,recommendation:`Use one icon family in each file so stroke, fill, and proportions agree.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-mixed-srcset-descriptors`,id:`no-mixed-srcset-descriptors`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mixed-srcset-descriptors`,title:`srcSet mixes width and density descriptors`,severity:`warn`,recommendation:`Use either width descriptors with sizes or density descriptors throughout one srcSet, never both.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-moment`,id:`no-moment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-moment`,title:`Using moment.js`,severity:`warn`,recommendation:"Switch to `import { format } from 'date-fns'` or `import dayjs from 'dayjs'` (2kb).",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-monotonous-page-spacing`,id:`no-monotonous-page-spacing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-monotonous-page-spacing`,title:`Page repeats one spacing value throughout`,severity:`warn`,recommendation:`Use deliberate spacing tiers to distinguish local groups, components, and page sections.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-multi-comp`,id:`no-multi-comp`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-multi-comp`,title:`Multiple components in one file`,severity:`warn`,recommendation:`Move secondary components into their own files so each component stays easier to find, test, and change.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-multiple-main-landmarks`,id:`no-multiple-main-landmarks`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-multiple-main-landmarks`,title:`View contains multiple main landmarks`,severity:`warn`,recommendation:`Keep one visible main landmark per rendered view so assistive-technology users can jump to the primary content unambiguously.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-multiple-unlabeled-navigation-landmarks`,id:`no-multiple-unlabeled-navigation-landmarks`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-multiple-unlabeled-navigation-landmarks`,title:`Repeated navigation landmarks need unique names`,severity:`warn`,recommendation:`Give each coexisting navigation landmark a concise, unique aria-label or aria-labelledby value.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-mutable-in-deps`,id:`no-mutable-in-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutable-in-deps`,title:`Mutable value in effect dependencies`,severity:`error`,recommendation:"Read mutable values like `location.pathname` or `ref.current` inside the effect body, or subscribe with `useSyncExternalStore`. Changing them doesn't redraw the screen, so listing them in deps won't make the effect run again.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-mutate-queried-dom-node-in-component`,id:`no-mutate-queried-dom-node-in-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutate-queried-dom-node-in-component`,title:`Mutating a queried DOM node this component renders`,severity:`warn`,recommendation:`Drive the node with state/props (or a ref for genuinely uncontrolled nodes) instead of querying it and mutating its style/class. Imperative edits to a node React renders are invisible to the virtual DOM and get reverted or clobbered on the next render.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-mutate-then-set-or-return-same-reference`,id:`no-mutate-then-set-or-return-same-reference`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutate-then-set-or-return-same-reference`,title:`State mutated in place then set by same reference`,severity:`warn`,recommendation:`Copy state before mutating it, then pass the fresh reference to the matching useState setter.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-mutating-array-method-on-prop-or-hook-result`,id:`no-mutating-array-method-on-prop-or-hook-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutating-array-method-on-prop-or-hook-result`,title:`In-place array mutation on a prop or hook result`,severity:`warn`,recommendation:"`sort`, `reverse`, and `splice` mutate in place, so calling them on a prop or hook result corrupts shared state. Use the immutable `toSorted`/`toReversed`/`toSpliced`, or copy the array first (`[...array].sort()`) when targeting pre-ES2023 runtimes.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-mutating-reducer-state`,id:`no-mutating-reducer-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutating-reducer-state`,title:`Reducer mutates its state`,severity:`error`,recommendation:`Return a new state object from the reducer instead of changing the old one and returning it. React only notices the change when the object is new.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-namespace`,id:`no-namespace`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-namespace`,title:`Namespaced JSX element`,severity:`warn`,recommendation:"Use a plain component or DOM tag because React cannot render JSX namespaced names like `ns:Foo`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-nested-card-surface`,id:`no-nested-card-surface`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nested-card-surface`,title:`Card surface is nested inside another card`,severity:`warn`,recommendation:`Flatten the inner surface and use spacing, a divider, or typography to communicate the hierarchy.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-nested-component-definition`,id:`no-nested-component-definition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nested-component-definition`,title:`Component defined inside another component`,severity:`warn`,recommendation:`Move it to module scope or a separate file so React does not recreate the component and erase its state on every parent render.`,category:`Bugs`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-non-literal-selector-query-without-try-catch`,id:`no-non-literal-selector-query-without-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-non-literal-selector-query-without-try-catch`,title:`Unguarded querySelector with href-derived selector`,severity:`warn`,recommendation:"`querySelector`/`querySelectorAll`/`matches`/`closest` throw a `DOMException` on an invalid CSS selector, and href/hash fragments are frequently invalid. Wrap the call in try/catch or normalize the value with `CSS.escape`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-non-null-assertion-on-maybe-undefined-result`,id:`no-non-null-assertion-on-maybe-undefined-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-non-null-assertion-on-maybe-undefined-result`,title:`Non-null assertion on a maybe-undefined result`,severity:`warn`,recommendation:"Drop the `!` on `.find`/`.match`/`.get` results and handle the miss (optional chaining, a guard, or a fallback). These built-ins return `undefined`/`null` when nothing matches, so the assertion just moves the crash one line later.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-nondeterministic-id-value-in-render-body`,id:`no-nondeterministic-id-value-in-render-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nondeterministic-id-value-in-render-body`,title:`Nondeterministic id generated in render body`,severity:`warn`,recommendation:`An id generator (uniqueId/nanoid/crypto.randomUUID/shortid) bound in the render body re-runs every render, so the id is unstable and breaks htmlFor/aria/SVG references and SSR hydration. Use useId for reference ids, or a useRef/useState initializer to mint it once.`,category:`Bugs`,framework:`global`,tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-noninteractive-element-interactions`,id:`no-noninteractive-element-interactions`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-noninteractive-element-interactions`,title:`Handler on non-interactive element`,severity:`warn`,recommendation:`Put interactions on a button or link, or add an interactive role.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-noninteractive-element-to-interactive-role`,id:`no-noninteractive-element-to-interactive-role`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-noninteractive-element-to-interactive-role`,title:`Noninteractive element given interactive role`,severity:`warn`,recommendation:`Use a real interactive element instead of adding an interactive role to a static one.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-noninteractive-tabindex`,id:`no-noninteractive-tabindex`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-noninteractive-tabindex`,title:`Tabindex on non-interactive element`,severity:`warn`,recommendation:"Only add `tabIndex` to interactive elements or interactive roles.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-nonresizable-textarea`,id:`no-nonresizable-textarea`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nonresizable-textarea`,title:`Textarea resizing is disabled`,severity:`warn`,recommendation:`Let users resize multiline fields in at least the block direction unless the control grows automatically with its content.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-nullish-coalescing-arithmetic-precedence`,id:`no-nullish-coalescing-arithmetic-precedence`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nullish-coalescing-arithmetic-precedence`,title:`Nullish coalescing swallowed by adjacent arithmetic`,severity:`warn`,recommendation:"Arithmetic binds tighter than `??`, so wrap the nullish part in parentheses (`(x ?? 0) / y`) to compute the value you actually intend.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-numbered-section-markers`,id:`no-numbered-section-markers`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-numbered-section-markers`,title:`Styled numbers are used as section decoration`,severity:`warn`,recommendation:`Remove decorative section numbering unless the sequence communicates real progress or ordered steps.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-object-keys-values-entries-on-maybe-undefined`,id:`no-object-keys-values-entries-on-maybe-undefined`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-object-keys-values-entries-on-maybe-undefined`,title:`Object.keys/values/entries on maybe-undefined value`,severity:`warn`,recommendation:"`Object.keys`, `Object.values`, and `Object.entries` throw on `undefined`/`null`, so pass a `?? {}` fallback or guard the value with a null check before calling them.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-object-or-array-coerced-to-string-in-template-literal`,id:`no-object-or-array-coerced-to-string-in-template-literal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-object-or-array-coerced-to-string-in-template-literal`,title:`Object or lossy array coerced in a template literal`,severity:`warn`,recommendation:"Interpolating an object or structurally nested array can produce `[object Object]` or lose structure; read a specific value or use `.join`/`JSON.stringify`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-outline-none`,id:`no-outline-none`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-outline-none`,title:`outline:none removes focus ring`,severity:`warn`,recommendation:"Style `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px }` so the focus ring shows for keyboard users but not mouse clicks.",category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-overloaded-hover-state`,id:`no-overloaded-hover-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-overloaded-hover-state`,title:`Hover state stacks too many effects`,severity:`warn`,recommendation:`Use one restrained hover idea, such as a color shift or a small shadow change, instead of stacking motion, color, and depth effects.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-oversized-long-heading`,id:`no-oversized-long-heading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-oversized-long-heading`,title:`Long headline uses an oversized display scale`,severity:`warn`,recommendation:`Reduce the display size for sentence-length headlines, or tighten the copy before using a hero scale.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-overwide-text-measure`,id:`no-overwide-text-measure`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-overwide-text-measure`,title:`Text measure is too wide`,severity:`warn`,recommendation:`Constrain long-form text to a readable line length, usually between 60ch and 75ch.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pass-data-to-parent`,id:`no-pass-data-to-parent`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-pass-data-to-parent`,title:`Data passed to parent via effect`,severity:`warn`,recommendation:`Fetch the data in the parent and pass it down as a prop (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-pass-live-state-to-parent`,id:`no-pass-live-state-to-parent`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-pass-live-state-to-parent`,title:`Live state pushed to parent via effect`,severity:`warn`,recommendation:`Move the state up to the parent (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-passive-request-owner-ref`,id:`no-passive-request-owner-ref`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-passive-request-owner-ref`,title:`Passive owner ref leaves a stale-request window`,severity:`warn`,recommendation:"Invalidate requests when the owner changes before an old promise can settle, or bind the request to lifecycle cleanup instead of trusting a ref synchronized by `useEffect`.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-path-prefix-containment`,id:`no-path-prefix-containment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-path-prefix-containment`,title:`Path containment check uses a string prefix`,severity:`warn`,recommendation:"Use `path.relative(root, candidate)` and reject `..` or absolute results instead of comparing path strings with the bare root prefix.",category:`Security`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-permanent-will-change`,id:`no-permanent-will-change`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-permanent-will-change`,title:`Permanent will-change wastes GPU`,severity:`warn`,recommendation:"Add will-change when the animation starts (`onMouseEnter`) and remove it when it ends (`onAnimationEnd`). Leaving it on all the time wastes GPU memory and can slow things down",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-pill-navigation-count`,id:`no-pill-navigation-count`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pill-navigation-count`,title:`Navigation count uses a pill badge`,severity:`warn`,recommendation:`Align navigation counts as plain tabular text instead of decorating each value as a pill.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-placeholder-only-field`,id:`no-placeholder-only-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-placeholder-only-field`,title:`Field relies on placeholder text for its label`,severity:`warn`,recommendation:`Add a visible label associated with the field. Keep placeholder text for examples or formatting hints.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-placeholder-persona-copy`,id:`no-placeholder-persona-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-placeholder-persona-copy`,title:`Page renders a placeholder persona`,severity:`warn`,recommendation:`Use realistic, context-specific sample content or clearly label the surface as a demo instead of shipping generic placeholder identities.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pointer-disabled-enabled-control`,id:`no-pointer-disabled-enabled-control`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pointer-disabled-enabled-control`,title:`Enabled control ignores pointer input`,severity:`warn`,recommendation:"Remove `pointer-events: none` from enabled controls so mouse and touch users can operate them, or mark the control unavailable with native disabled semantics.",category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-polymorphic-children`,id:`no-polymorphic-children`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-polymorphic-children`,title:`Children type checked at runtime`,severity:`warn`,recommendation:"Add clear subcomponents like `<Button.Text>` and `<Button.Icon>` so callers don't have to check `typeof children`.",category:`Maintainability`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-predicate-function-reference-in-boolean-position`,id:`no-predicate-function-reference-in-boolean-position`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-predicate-function-reference-in-boolean-position`,title:`Predicate function used without calling it`,severity:`warn`,recommendation:"A bare `is*`/`has*`/`can*`/`should*`/`will*` function reference is always truthy in a condition, so the guarded branch never behaves as intended. Call the function (`isReady()`) to evaluate the predicate.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-presentation-role-conflict`,id:`no-presentation-role-conflict`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-presentation-role-conflict`,title:`Presentational element exposes conflicting semantics`,severity:`warn`,recommendation:`Remove the presentational role, focusability, or global ARIA state so assistive technologies receive one consistent semantic model.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-prevent-default`,id:`no-prevent-default`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prevent-default`,title:`preventDefault on a form or link`,severity:`warn`,recommendation:"Use `<form action>` where your framework supports it (it works without JS), or use a `<button>` instead of an `<a>` with preventDefault.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-promise-then-side-effect-in-effect-without-catch`,id:`no-promise-then-side-effect-in-effect-without-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-promise-then-side-effect-in-effect-without-catch`,title:`Effect promise .then sets state with no catch`,severity:`warn`,recommendation:"An async init in an effect that sets state in `.then` but has no `.catch` leaves the component stuck and raises an unhandled rejection when it fails. Add a `.catch` on the chain (`.finally` does not handle the rejection).",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-prop-callback-in-effect`,id:`no-prop-callback-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prop-callback-in-effect`,title:`Parent kept in sync with a callback effect`,severity:`warn`,recommendation:`Move the shared state into a Provider so both sides read the same value. Then you don't need a useEffect to keep them in sync.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-prop-callback-in-render`,id:`no-prop-callback-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prop-callback-in-render`,title:`Prop callback invoked during render`,severity:`error`,recommendation:`Invoke the callback from the event or asynchronous operation that produced the value, or from an effect when synchronizing with an external system. Render must stay pure because React can replay or discard it.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-prop-types`,id:`no-prop-types`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prop-types`,title:`propTypes ignored in React 19`,severity:`warn`,recommendation:"React 19 ignores `Component.propTypes`, so invalid props pass silently. Describe props with TypeScript types and add real runtime checks (or schema parsing) only where data can actually be wrong. Only runs on React 19+ projects.",category:`Maintainability`,framework:`global`,requires:[`react:19`],tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pulsing-status-dot`,id:`no-pulsing-status-dot`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pulsing-status-dot`,title:`Decorative status dot pulses continuously`,severity:`warn`,recommendation:`Use a static status indicator unless the element communicates real work in progress.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pure-black-background`,id:`no-pure-black-background`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pure-black-background`,title:`Pure black background`,severity:`warn`,recommendation:"Nudge the background slightly toward your brand color, like `#0a0a0f` or Tailwind's `bg-gray-950`. Pure black looks harsh on modern screens.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pure-black-shadow`,id:`no-pure-black-shadow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pure-black-shadow`,title:`Surface uses a pure-black shadow`,severity:`warn`,recommendation:`Tint shadows toward the surrounding surface or use a softer neutral token instead of pure black.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-radial-halo`,id:`no-radial-halo`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-radial-halo`,title:`Saturated radial halo on a dark surface`,severity:`warn`,recommendation:`Use restrained surface contrast, product-specific imagery, or a subtler accent instead of a saturated radial halo on a dark page.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-random-key`,id:`no-random-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-random-key`,title:`Random value used as a key`,severity:`error`,recommendation:"Use a stable id from the item itself, like `item.id`, a content hash, or the index when the order never changes. Don't build the key from something that changes every time.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-react-children`,id:`no-react-children`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-react-children`,title:`React.Children is fragile when child shape changes`,severity:`warn`,recommendation:`Pass children as explicit props or render them directly so child shape changes do not break traversal logic.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-react-dom-deprecated-apis`,id:`no-react-dom-deprecated-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-react-dom-deprecated-apis`,title:`Deprecated react-dom APIs break in React 19`,severity:`warn`,recommendation:"Switch the old `react-dom` root API (`render` / `hydrate` / `unmountComponentAtNode`) to `createRoot` / `hydrateRoot` / `root.unmount()` from `react-dom/client`. Replace `findDOMNode` with a ref. `react-dom/test-utils` is gone in React 19, so use `act` from `react` and `fireEvent` / `render` from `@testing-library/react`. Only runs on React 18+ projects.",category:`Maintainability`,framework:`global`,requires:[`react:18`],tags:[`test-noise`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-react19-deprecated-apis`,id:`no-react19-deprecated-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-react19-deprecated-apis`,title:`React 19 API migration can break callers`,severity:`warn`,recommendation:`Replace removed React APIs with their supported React 19 alternatives. Only runs on React 19+ projects.`,category:`Maintainability`,framework:`global`,requires:[`react:19`],tags:[`test-noise`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-reduced-motion-content-removal`,id:`no-reduced-motion-content-removal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-reduced-motion-content-removal`,title:`Reduced motion removes meaningful content`,severity:`warn`,recommendation:`Keep the same content and actions available under reduced motion, replacing spatial movement with a static or non-spatial presentation.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-redundant-display-class`,id:`no-redundant-display-class`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-redundant-display-class`,title:`Redundant display utility`,severity:`warn`,recommendation:"Drop the display class that matches the element's default (`block` on a `<div>`, `inline` on a `<span>`). It is pure noise; keep only display changes like `flex`, `grid`, or `hidden`.",category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-redundant-roles`,id:`no-redundant-roles`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-redundant-roles`,title:`Redundant ARIA role`,severity:`warn`,recommendation:"Remove redundant `role` attributes so assistive tech reads the element's native semantics without extra noise.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-redundant-should-component-update`,id:`no-redundant-should-component-update`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-redundant-should-component-update`,title:`Redundant shouldComponentUpdate`,severity:`warn`,recommendation:"Drop `shouldComponentUpdate` (PureComponent already shallow-compares) or extend `React.Component` if custom logic is needed.",category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-redundant-title-tooltip`,id:`no-redundant-title-tooltip`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-redundant-title-tooltip`,title:`Title tooltip repeats visible control text`,severity:`warn`,recommendation:`Remove tooltips that merely repeat a control's visible label.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-ref-callback-cleanup-before-react-19`,id:`no-ref-callback-cleanup-before-react-19`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ref-callback-cleanup-before-react-19`,title:`Ref cleanup requires React 19`,severity:`warn`,recommendation:"React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",category:`Bugs`,framework:`global`,requires:[`react:18`],disabledWhen:[`react:19`],isScanRule:!1}},{key:`react-doctor/no-ref-current-in-render`,id:`no-ref-current-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ref-current-in-render`,title:`Ref mutated during render`,severity:`error`,recommendation:`Move ref writes into an event handler or effect. Render must stay pure because React can replay or discard it. The predictable null-guarded lazy initialization pattern remains supported.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-render-in-render`,id:`no-render-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-render-in-render`,title:`Component rendered by inline function call`,severity:`warn`,recommendation:`Make it a named component rendered as JSX so React can track it and preserve its state.`,category:`Maintainability`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-render-prop-children`,id:`no-render-prop-children`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-render-prop-children`,title:`Render-prop slots make this component hard to extend`,severity:`warn`,recommendation:"Swap `renderXxx` props for child components like `<Modal.Header>` or plain `children`, so the parent doesn't control every slot.",category:`Maintainability`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-render-return-value`,id:`no-render-return-value`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-render-return-value`,title:`Using ReactDOM.render return value`,severity:`warn`,recommendation:"Don't use `ReactDOM.render`'s return value. It's legacy and was removed in React 19.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-repeated-container-text`,id:`no-repeated-container-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-container-text`,title:`A card repeats the same text in distinct slots`,severity:`warn`,recommendation:`Keep repeated status or label copy in the one card slot where it carries the clearest meaning.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-emoji-tiles`,id:`no-repeated-emoji-tiles`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-emoji-tiles`,title:`Page repeats boxed emoji as feature icons`,severity:`warn`,recommendation:`Use a coherent icon set or product-specific artwork instead of repeating platform-dependent emoji tiles.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-glass-surfaces`,id:`no-repeated-glass-surfaces`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-glass-surfaces`,title:`Page repeats glass panels`,severity:`warn`,recommendation:`Reserve translucency for one overlay or focal layer and use quieter opaque surfaces elsewhere.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-hover-scale`,id:`no-repeated-hover-scale`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-hover-scale`,title:`Hover scaling repeats across the page`,severity:`warn`,recommendation:`Reserve hover movement for a specific spatial interaction instead of making unrelated cards and tiles all grow under the pointer.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-kicker-labels`,id:`no-repeated-kicker-labels`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-kicker-labels`,title:`Repeated tracked labels scaffold multiple sections`,severity:`warn`,recommendation:`Use stronger section composition or reserve the decorative kicker treatment for one meaningful moment.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-placeholder-navigation`,id:`no-repeated-placeholder-navigation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-placeholder-navigation`,title:`Navigation repeats placeholder destinations`,severity:`warn`,recommendation:`Replace placeholder destinations with real routes or render non-interactive navigation previews until destinations exist.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-section-shells`,id:`no-repeated-section-shells`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-section-shells`,title:`Page repeats the same padded section shell`,severity:`warn`,recommendation:`Vary section composition and rhythm instead of wrapping every block in the same large padding and centered max-width container.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeating-gradient-decoration`,id:`no-repeating-gradient-decoration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeating-gradient-decoration`,title:`Surface uses a repeating gradient texture`,severity:`warn`,recommendation:`Use a deliberate texture asset or a quiet solid surface instead of repeating-gradient decoration.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-reset-all-state-on-prop-change`,id:`no-reset-all-state-on-prop-change`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-reset-all-state-on-prop-change`,title:`All state reset on prop change`,severity:`warn`,recommendation:"Pass the prop as `key` so React resets the component for you when the prop changes, instead of clearing every state value by hand in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-responsive-hidden-accessible-name`,id:`no-responsive-hidden-accessible-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-responsive-hidden-accessible-name`,title:`Responsive styles hide a control's accessible name`,severity:`warn`,recommendation:`Keep an accessible name available at every breakpoint, such as persistent sr-only text or an aria-label on the control.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-scale-from-zero`,id:`no-scale-from-zero`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-scale-from-zero`,title:`Animating scale from zero`,severity:`warn`,recommendation:"Use `initial={{ scale: 0.95, opacity: 0 }}`. Elements should gently shrink and fade, not vanish into a point",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-secrets-in-client-code`,id:`no-secrets-in-client-code`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-secrets-in-client-code`,title:`Secret in client code`,severity:`warn`,recommendation:`Move secrets to server-only code. Anything in client env variables gets shipped to the browser, so it can't hold secrets.`,recommendationOverride:`client-secret`,category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-self-updating-effect`,id:`no-self-updating-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-self-updating-effect`,title:`Effect updates its own dependency`,severity:`warn`,recommendation:`Break the loop: work the value out while rendering, set it in an event handler, or guard the update so it stops changing. See https://react.dev/learn/you-might-not-need-an-effect`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-server-side-image-map`,id:`no-server-side-image-map`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-server-side-image-map`,title:`Server-side image map`,severity:`warn`,recommendation:`Replace isMap with semantic links or a client-side map that exposes each target.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-set-state`,id:`no-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-set-state`,title:`Local class state forbidden`,severity:`warn`,recommendation:`Lift state up or use an external store so class-local state does not hide ownership.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-set-state-after-await-in-effect`,id:`no-set-state-after-await-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-set-state-after-await-in-effect`,title:`State update after await in an effect`,severity:`warn`,recommendation:"In a `useEffect` whose dependencies can change, guard any setter call that runs after an `await` behind a cancellation/ignore flag, or return a cleanup that cancels the async work.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-set-state-in-render`,id:`no-set-state-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-set-state-in-render`,title:`setState called during render`,severity:`warn`,recommendation:"Move the setter into a `useEffect` or an event handler, or compute the value while rendering. Calling a setter during render starts another render that calls it again, looping forever.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-shape-assembled-illustration`,id:`no-shape-assembled-illustration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-shape-assembled-illustration`,title:`Large illustration is assembled from primitive shapes`,severity:`warn`,recommendation:`Replace the shape pile with deliberate artwork, a photograph, or a purpose-built graphic that supports the product story.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-side-effect-in-state-updater-function`,id:`no-side-effect-in-state-updater-function`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-side-effect-in-state-updater-function`,title:`Side effect inside a state updater function`,severity:`warn`,recommendation:`React may replay a state updater, so callbacks, analytics, and persistence inside it can run more than once. Compute state purely, then perform the side effect outside the setter.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-side-tab-border`,id:`no-side-tab-border`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-side-tab-border`,title:`Thick one-sided stripe`,severity:`warn`,recommendation:`Use a background change, a thin neutral edge, or a subtler surface treatment instead of a thick one-sided stripe.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-skipped-heading-level`,id:`no-skipped-heading-level`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-skipped-heading-level`,title:`Heading hierarchy skips a level`,severity:`warn`,recommendation:`Keep headings in a continuous hierarchy inside a page or article so assistive navigation reflects the visual structure.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-small-form-control-text`,id:`no-small-form-control-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-small-form-control-text`,title:`Form control text is smaller than 16px`,severity:`warn`,recommendation:`Use at least 16px text on mobile inputs, selects, and textareas so content remains readable and mobile browsers do not zoom unexpectedly.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-smooth-scroll-without-reduced-motion`,id:`no-smooth-scroll-without-reduced-motion`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-smooth-scroll-without-reduced-motion`,title:`Smooth scrolling ignores reduced motion`,severity:`warn`,recommendation:`Enable smooth scrolling only for users without a reduced-motion preference, and fall back to instant scrolling for everyone else.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-spread-accumulator-in-reduce`,id:`no-spread-accumulator-in-reduce`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-spread-accumulator-in-reduce`,title:`Accumulator spread in reduce is quadratic`,severity:`warn`,recommendation:"Mutate the accumulator and return it (`acc[key] = value; return acc`) so the fold stays O(n) instead of copying the whole accumulator every step.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-spread-props-over-defaults-clobbers-with-undefined`,id:`no-spread-props-over-defaults-clobbers-with-undefined`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-spread-props-over-defaults-clobbers-with-undefined`,title:`Spread props over defaults can clobber with undefined`,severity:`warn`,recommendation:`An explicit undefined prop can overwrite a default. Reapply defaults with ?? at the merge or use site, or remove undefined keys before spreading props.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-srcset-without-sizes`,id:`no-srcset-without-sizes`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-srcset-without-sizes`,title:`Responsive image omits sizes`,severity:`warn`,recommendation:`Add a sizes attribute that describes the image's rendered width at each responsive breakpoint.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-stale-timer-ref`,id:`no-stale-timer-ref`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-stale-timer-ref`,title:`Cleared timer ref keeps the stale id`,severity:`warn`,recommendation:"Reset the ref right after clearing (`clearTimeout(ref.current); ref.current = null`) so truthiness checks on the ref keep meaning “timer still pending”.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-static-element-interactions`,id:`no-static-element-interactions`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-static-element-interactions`,title:`Interaction on static element`,severity:`warn`,recommendation:"Give clickable static elements a `role`, or use a button or link.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-static-motion-config-never`,id:`no-static-motion-config-never`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-static-motion-config-never`,title:`MotionConfig always ignores reduced motion`,severity:`warn`,recommendation:'Use `reducedMotion="user"`, or derive the value from an explicit user preference instead of permanently disabling reduced-motion support.',category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-string-false-on-boolean-attribute`,id:`no-string-false-on-boolean-attribute`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-string-false-on-boolean-attribute`,title:`String true/false on a boolean attribute`,severity:`warn`,recommendation:'Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled="false"`. A non-empty string is truthy, so `="false"` actually turns the attribute ON.',category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-string-refs`,id:`no-string-refs`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-string-refs`,title:`String refs are legacy and fragile`,severity:`warn`,recommendation:"Use a callback ref or `useRef` so ref ownership is explicit and not tied to legacy string lookup.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-svg-currentcolor-with-fill-class`,id:`no-svg-currentcolor-with-fill-class`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-svg-currentcolor-with-fill-class`,title:`currentColor fights a fill/stroke class`,severity:`warn`,recommendation:'Pick one source of truth: drop the `fill="currentColor"` attribute and keep the `fill-*` class, or use `fill-current` to inherit the text color. Having both means the class silently wins.',category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-symmetric-text-button-padding`,id:`no-symmetric-text-button-padding`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-symmetric-text-button-padding`,title:`Text button uses symmetric padding`,severity:`warn`,recommendation:`Give text buttons more horizontal than vertical padding to preserve their control shape.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-sync-xhr`,id:`no-sync-xhr`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-sync-xhr`,title:`Synchronous XMLHttpRequest`,severity:`warn`,recommendation:"Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",category:`Performance`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-tailwind-layout-transition`,id:`no-tailwind-layout-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tailwind-layout-transition`,title:`Animating a layout property`,severity:`warn`,recommendation:"Animate `transform` and `opacity` instead, since they skip layout and run on the compositor. For height, animate `grid-template-rows` from `0fr` to `1fr`.",category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-this-in-sfc`,id:`no-this-in-sfc`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-this-in-sfc`,title:`this used in function component`,severity:`warn`,recommendation:"Read from the `props` argument because function components do not have a React instance `this`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-tight-all-caps-heading`,id:`no-tight-all-caps-heading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tight-all-caps-heading`,title:`Long all-caps heading has collision-prone leading`,severity:`warn`,recommendation:`Use at least 1.0 line-height for an all-caps display heading that can wrap, or switch the heading to ordinary casing.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tight-body-leading`,id:`no-tight-body-leading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tight-body-leading`,title:`Body copy has cramped line spacing`,severity:`warn`,recommendation:`Use a line height of at least 1.3 for multi-line body text.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tight-display-tracking`,id:`no-tight-display-tracking`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tight-display-tracking`,title:`Display heading uses the tightest tracking preset`,severity:`warn`,recommendation:`Use normal or moderately tight tracking so display text keeps clear character shapes across fonts and viewport sizes.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tiny-text`,id:`no-tiny-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tiny-text`,title:`Text is too small`,severity:`warn`,recommendation:`Use at least 12px for body text, and 16px is best. Small text is hard to read, especially on phones.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tiny-uppercase-tracked-label`,id:`no-tiny-uppercase-tracked-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tiny-uppercase-tracked-label`,title:`Tiny label combines uppercase text and decorative tracking`,severity:`warn`,recommendation:`Use ordinary interface casing at a readable size instead of shrinking and spacing out short labels.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-transition-all`,id:`no-transition-all`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-transition-all`,title:`transition: all animates everything`,severity:`warn`,recommendation:'List the specific properties: `transition: "opacity 200ms, transform 200ms"`. In Tailwind, use `transition-colors`, `transition-opacity`, or `transition-transform`',category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-transitioned-composite-widget-state`,id:`no-transitioned-composite-widget-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-transitioned-composite-widget-state`,title:`Composite widget state feedback is delayed`,severity:`warn`,recommendation:`Keep selected, checked, and current-item color feedback instant in options, menus, and trees. Reserve transitions for the widget container opening or closing.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`react-jsx-only`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-transitioned-focus-ring`,id:`no-transitioned-focus-ring`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-transitioned-focus-ring`,title:`Focus indicator animates into view`,severity:`warn`,recommendation:`Keep keyboard focus indicators instant. Restrict transitions to hover colors or transforms that do not delay the focus ring.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unbounded-animation-frame-loop`,id:`no-unbounded-animation-frame-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unbounded-animation-frame-loop`,title:`Animation frame loop has no stop path`,severity:`warn`,recommendation:`Add an explicit stop condition and retain each request ID so the active frame can be cancelled when the animation or component stops.`,category:`Performance`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-uncontrolled-input`,id:`no-uncontrolled-input`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uncontrolled-input`,title:`Uncontrolled input value`,severity:`warn`,recommendation:'Give `useState` a starting value (e.g. `useState("")` instead of `useState()`), add `onChange` (or `readOnly`) whenever you set `value`, and drop `defaultValue` on controlled inputs since React ignores it.',category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-undeferred-third-party`,id:`no-undeferred-third-party`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-undeferred-third-party`,title:`Render-blocking third-party script`,severity:`warn`,recommendation:'Use `next/script` with `strategy="lazyOnload"`, or add the `defer` attribute.',category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-undersized-icon-button`,id:`no-undersized-icon-button`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-undersized-icon-button`,title:`Icon button target is smaller than 24px`,severity:`warn`,recommendation:`Make the button at least 24 by 24 CSS pixels, or provide enough surrounding target spacing to satisfy the WCAG exception.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unescaped-dynamic-string-in-regexp`,id:`no-unescaped-dynamic-string-in-regexp`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unescaped-dynamic-string-in-regexp`,title:`Unescaped dynamic string in RegExp constructor`,severity:`warn`,recommendation:"A dynamic literal string such as a search term or path segment dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so values containing `.` or `(` over-match or throw. Escape the value with an `escapeRegExp` helper before constructing the pattern.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-unescaped-entities`,id:`no-unescaped-entities`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unescaped-entities`,title:`Unescaped entities in JSX`,severity:`warn`,recommendation:"Replace bare `'` / `\"` / `>` / `}` characters with HTML entities so literal UI text is encoded consistently.",category:`Bugs`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-ungated-tailwind-animation`,id:`no-ungated-tailwind-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ungated-tailwind-animation`,title:`Tailwind animation ignores reduced motion`,severity:`warn`,recommendation:`Gate motion with motion-safe or provide a motion-reduce animation override that preserves the same information without spatial movement.`,category:`Accessibility`,framework:`global`,requires:[`react`,`tailwind`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unguarded-browser-global-at-module-scope`,id:`no-unguarded-browser-global-at-module-scope`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-browser-global-at-module-scope`,title:`Browser global read at module scope`,severity:`warn`,recommendation:'Reading `window`/`navigator`/`localStorage` at module scope throws `ReferenceError: window is not defined` when the module is imported during SSR. Move the read inside a function/effect, or guard it with `typeof window !== "undefined"`.',category:`Bugs`,framework:`global`,requires:[`ssr`],isScanRule:!1}},{key:`react-doctor/no-unguarded-browser-global-in-render-or-hook-init`,id:`no-unguarded-browser-global-in-render-or-hook-init`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-browser-global-in-render-or-hook-init`,title:`Browser global read during server render`,severity:`error`,recommendation:`Move browser-only reads into an effect or event, guard them behind a client-only render path, or use useSyncExternalStore with a stable server snapshot.`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-unguarded-numeric-input-parse`,id:`no-unguarded-numeric-input-parse`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-numeric-input-parse`,title:`Unguarded numeric parse of an input value`,severity:`warn`,recommendation:"Guard `Number(e.target.value)` / `parseInt(e.target.value)` against empty and NaN before storing it. `Number('')` is `0` and `Number('abc')` is `NaN`, both of which silently ship a wrong value.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-unguarded-throwing-parse-call`,id:`no-unguarded-throwing-parse-call`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-throwing-parse-call`,title:`Unguarded call to a throwing parse API`,severity:`warn`,recommendation:"`decodeURIComponent`/`decodeURI`, color parsers (`readableColor`/`parseToRgb`/`chroma`), and single-arg `new URL(x)` on a URL/route value throw on malformed runtime input and crash render; guard with a validity pre-check (`URL.canParse`, `chroma.valid`), a try/catch, or a `safe*` helper that returns a fallback.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-uniform-feature-card-grid`,id:`no-uniform-feature-card-grid`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uniform-feature-card-grid`,title:`Feature grid repeats one card recipe`,severity:`warn`,recommendation:`Vary the composition, group related capabilities, or choose a denser list when every feature receives the same card weight.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-uninformative-aria-label`,id:`no-uninformative-aria-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uninformative-aria-label`,title:`Uninformative aria-label`,severity:`warn`,recommendation:'Name the action, not the element type: `aria-label="Search"`, not `aria-label="icon"` or `aria-label="button"`.',category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-unknown-property`,id:`no-unknown-property`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unknown-property`,title:`Unknown DOM property`,severity:`warn`,recommendation:"Use the prop name React expects, like `className`, `htmlFor`, or `tabIndex`, so the attribute is applied correctly.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-unowned-async-error-clear`,id:`no-unowned-async-error-clear`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unowned-async-error-clear`,title:`Stale async response clears newer request state`,severity:`warn`,recommendation:`Guard async completion by request identity, perform an ownership-aware functional state update, or key the state-owning component by request ID.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unsafe`,id:`no-unsafe`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unsafe`,title:`Unsafe legacy lifecycle method`,severity:`warn`,recommendation:"Move setup to `constructor` or `componentDidMount`, prop-derived state to `getDerivedStateFromProps`, and update side effects to `componentDidUpdate` so React does not rely on deprecated unsafe lifecycles.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-unsafe-json-parse`,id:`no-unsafe-json-parse`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unsafe-json-parse`,title:`Unsafe JSON.parse dereference`,severity:`warn`,recommendation:"Wrap `JSON.parse(x)` in try/catch and validate the result (for example with a schema) before reading properties off it. A bare `JSON.parse(x).foo` throws on bad input and lets undefined fields slip past the type-checker.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-unstable-nested-components`,id:`no-unstable-nested-components`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unstable-nested-components`,title:`Component defined inside a component`,severity:`warn`,recommendation:`Move nested components to module scope so React does not remount them and lose state on every render.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-unthrottled-scroll-mutation`,id:`no-unthrottled-scroll-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unthrottled-scroll-mutation`,title:`Scroll handler drives animation every event`,severity:`warn`,recommendation:`Use a scroll or view timeline, IntersectionObserver, or a real timer throttle so animation work does not run for every scroll event.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-uppercase-mono-label`,id:`no-uppercase-mono-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uppercase-mono-label`,title:`Short label uses uppercase monospace styling`,severity:`warn`,recommendation:`Use the interface typeface and ordinary casing for labels; reserve monospace for values that are genuinely code-like.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-uppercase-tracked-navigation-label`,id:`no-uppercase-tracked-navigation-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uppercase-tracked-navigation-label`,title:`Navigation label uses uppercase tracking`,severity:`warn`,recommendation:`Use ordinary casing and tracking for persistent navigation labels.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-usememo-simple-expression`,id:`no-usememo-simple-expression`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-usememo-simple-expression`,title:`useMemo on a cheap value`,severity:`warn`,recommendation:`Remove the useMemo. Property reads, math, and ternaries are already fast, so wrapping them doesn't help`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-whole-object-default-losing-per-key-defaults`,id:`no-whole-object-default-losing-per-key-defaults`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-whole-object-default-losing-per-key-defaults`,title:`Whole-object param default loses per-key defaults`,severity:`warn`,recommendation:"A whole-object parameter default applies only when the argument is omitted entirely, so a partial argument makes every omitted key undefined. Move each fallback onto its own binding instead: `({ a = 1, b = false } = {})`.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-whole-object-dep-with-member-reads`,id:`no-whole-object-dep-with-member-reads`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-whole-object-dep-with-member-reads`,title:`Whole props object in deps while only members are read`,severity:`warn`,recommendation:`Destructure the fields you read and depend on those bindings instead of the whole props object.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-wide-letter-spacing`,id:`no-wide-letter-spacing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-wide-letter-spacing`,title:`Wide letter spacing on body text`,severity:`warn`,recommendation:`Save wide letter-spacing (over 0.05em) for short uppercase labels, nav items, and buttons, not body text.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-will-update-set-state`,id:`no-will-update-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-will-update-set-state`,title:`setState in componentWillUpdate`,severity:`warn`,recommendation:`Avoid setState in componentWillUpdate because it can loop forever; derive state before render or move guarded updates to componentDidUpdate.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-z-index-9999`,id:`no-z-index-9999`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-z-index-9999`,title:`Excessively high z-index`,severity:`warn`,recommendation:"Pick a small z-index scale, like dropdown 10, modal 20, toast 30. To layer something on top, use `isolation: isolate` instead of bigger numbers.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/nosql-injection-risk`,id:`nosql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nosql-injection-risk`,title:`NoSQL query accepts operator-shaped input`,severity:`warn`,recommendation:"Coerce scalar fields before querying, reject operator keys from client input, and avoid `$where` or request-derived regexes.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/only-export-components`,id:`only-export-components`,source:`react-doctor`,originallyExternal:!0,rule:{id:`only-export-components`,title:`Non-component export in component file`,severity:`warn`,recommendation:`Move non-component exports out of component files so Fast Refresh can preserve component state instead of full-reloading.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/package-metadata-secret`,id:`package-metadata-secret`,source:`react-doctor`,originallyExternal:!1,rule:{id:`package-metadata-secret`,title:`Secret-like package metadata`,severity:`warn`,recommendation:`Keep secrets out of package metadata and generated reports; they are often published to registries, logs, or browser artifacts.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/path-traversal-risk`,id:`path-traversal-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`path-traversal-risk`,title:`Filesystem path uses caller input`,severity:`warn`,recommendation:`Resolve paths against a fixed base directory, reject traversal after normalization, and map user-visible identifiers to server-owned paths.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/plugin-update-trust-risk`,id:`plugin-update-trust-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`plugin-update-trust-risk`,title:`Plugin or updater trust boundary risk`,severity:`warn`,recommendation:`Require signed updates/plugins, pin trusted repositories, verify hashes before execution, and keep custom repository installs behind explicit warnings.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/pointer-capture-needs-cancel-handler`,id:`pointer-capture-needs-cancel-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`pointer-capture-needs-cancel-handler`,title:`Captured pointer interaction has no cancellation path`,severity:`warn`,recommendation:"Handle `onPointerCancel` or `onLostPointerCapture` with the same cleanup used for pointer-up so interrupted drags cannot stay active.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/postmessage-origin-risk`,id:`postmessage-origin-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`postmessage-origin-risk`,title:`postMessage handler without origin check`,severity:`warn`,recommendation:"Validate `event.origin` against an exact allowlist before using `event.data`, especially when an iframe or parent window can be attacker-controlled.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/preact-no-children-length`,id:`preact-no-children-length`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-no-children-length`,title:`Array methods on Preact children can crash`,severity:`warn`,recommendation:"Wrap with `toChildArray(children)` because Preact's `props.children` is not always an array and array methods can crash.",category:`Bugs`,framework:`preact`,requires:[`preact`],isScanRule:!1}},{key:`react-doctor/preact-no-react-hooks-import`,id:`preact-no-react-hooks-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-no-react-hooks-import`,title:`React hook imports break pure Preact hook state`,severity:`warn`,recommendation:"Import hooks from `preact/hooks` so they share Preact's renderer state instead of loading a second hook implementation.",category:`Bugs`,framework:`preact`,requires:[`pure-preact`],isScanRule:!1}},{key:`react-doctor/preact-no-render-arguments`,id:`preact-no-render-arguments`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-no-render-arguments`,title:`render() reads props from arguments`,severity:`warn`,recommendation:"Read from `this.props` and `this.state` because `preact/compat` uses React's parameterless `render()` and positional props/state become undefined.",category:`Bugs`,framework:`preact`,requires:[`preact`],isScanRule:!1}},{key:`react-doctor/preact-prefer-ondblclick`,id:`preact-prefer-ondblclick`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-prefer-ondblclick`,title:`onDoubleClick instead of onDblClick`,severity:`warn`,recommendation:"Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",category:`Bugs`,framework:`preact`,requires:[`pure-preact`],isScanRule:!1}},{key:`react-doctor/preact-prefer-oninput`,id:`preact-prefer-oninput`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-prefer-oninput`,title:`onChange instead of onInput`,severity:`warn`,recommendation:"Replace `onChange` with `onInput` on text-like inputs, or use `preact/compat` which remaps `onChange` automatically.",category:`Bugs`,framework:`preact`,requires:[`pure-preact`],isScanRule:!1}},{key:`react-doctor/prefer-dvh-over-vh`,id:`prefer-dvh-over-vh`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-dvh-over-vh`,title:`Use dvh instead of vh for full height`,severity:`warn`,recommendation:"Prefer `dvh` over `vh` for full-height elements. `100vh` overflows under mobile browser chrome; `100dvh` tracks the visible viewport. (`h-dvh`/`min-h-dvh` need Tailwind 3.4+.)",category:`Maintainability`,framework:`global`,requires:[`tailwind:3.4`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-dynamic-import`,id:`prefer-dynamic-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-dynamic-import`,title:`Heavy library loaded eagerly`,severity:`warn`,recommendation:"Load it only when needed: `const Component = dynamic(() => import('library'), { ssr: false })` from next/dynamic, or React.lazy().",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-es6-class`,id:`prefer-es6-class`,source:`react-doctor`,originallyExternal:!0,rule:{id:`prefer-es6-class`,title:`createClass instead of ES6 class`,severity:`warn`,recommendation:"Pick one component style so readers do not have to switch between legacy `createReactClass` patterns and modern class components.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-explicit-variants`,id:`prefer-explicit-variants`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-explicit-variants`,title:`Prefer explicit variant components`,severity:`warn`,recommendation:"Replace boolean props that switch whole subtrees with explicit variant components, like `<ThreadComposer />` and `<EditMessageComposer />`, so each variant renders one clear path.",category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/prefer-function-component`,id:`prefer-function-component`,source:`react-doctor`,originallyExternal:!0,rule:{id:`prefer-function-component`,title:`Class component instead of function`,severity:`warn`,recommendation:`Rewrite the class component as a function component so state and effects use modern hook patterns instead of class lifecycles.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-html-dialog`,id:`prefer-html-dialog`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-html-dialog`,title:`Custom modal instead of dialog`,severity:`warn`,recommendation:'Replace the wrapper with `<dialog>` and open it with `dialog.showModal()`. For the trigger, prefer `<button commandfor="id" command="show-modal">` (Chrome 135+), or a `useRef` with `dialogRef.current?.showModal()`.',category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/prefer-module-scope-pure-function`,id:`prefer-module-scope-pure-function`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-module-scope-pure-function`,title:`Pure function rebuilt every render`,severity:`warn`,recommendation:`Move the function above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted work.`,category:`Maintainability`,framework:`global`,disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-module-scope-static-value`,id:`prefer-module-scope-static-value`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-module-scope-static-value`,title:`Static value rebuilt every render`,severity:`warn`,recommendation:`Move the value above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted and makes it look new every time.`,category:`Maintainability`,framework:`global`,disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-motion-transform-property`,id:`prefer-motion-transform-property`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-motion-transform-property`,title:`Motion individual transforms miss compositor acceleration`,severity:`warn`,recommendation:"For animation that must remain smooth while JavaScript is busy, animate one `transform` string instead of Motion's individual transform keys.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`design`,`opt-in`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-stable-empty-fallback`,id:`prefer-stable-empty-fallback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-stable-empty-fallback`,title:`Empty fallback rebuilt each render`,severity:`warn`,recommendation:"Make a `const EMPTY = []` (or `{}`) at module scope and use it as the `||` / `??` fallback, so the value stays the same each render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`,`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-tabular-numeric-data`,id:`prefer-tabular-numeric-data`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-tabular-numeric-data`,title:`Changing table numbers use proportional figures`,severity:`warn`,recommendation:"Apply `tabular-nums` to dynamic numeric columns so digits keep a stable width and align while values change.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-tag-over-role`,id:`prefer-tag-over-role`,source:`react-doctor`,originallyExternal:!0,rule:{id:`prefer-tag-over-role`,title:`Role used instead of HTML tag`,severity:`warn`,recommendation:`Use the matching HTML element when one exists so browsers and assistive tech get native semantics.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/prefer-truncate-shorthand`,id:`prefer-truncate-shorthand`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-truncate-shorthand`,title:`Use truncate shorthand`,severity:`warn`,recommendation:"Replace `overflow-hidden text-ellipsis whitespace-nowrap` with the single Tailwind `truncate` utility, which sets all three.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-use-effect-event`,id:`prefer-use-effect-event`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-use-effect-event`,title:`Effect re-subscribes on a changing callback`,severity:`warn`,recommendation:"Wrap the callback with `useEffectEvent(callback)` (React 19.2+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent",category:`Bugs`,framework:`global`,requires:[`react`,`react:19.2`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-use-sync-external-store`,id:`prefer-use-sync-external-store`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-use-sync-external-store`,title:`Hand-rolled external store subscription`,severity:`warn`,recommendation:"Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook gets this right during concurrent rendering and on the server; the hand-rolled version doesn't.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-useReducer`,id:`prefer-useReducer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-useReducer`,title:`Many related useState calls`,severity:`warn`,recommendation:"Group state that always changes together into `useReducer` so one dispatched action describes the whole transition instead of a list of setter calls.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/public-debug-artifact`,id:`public-debug-artifact`,source:`react-doctor`,originallyExternal:!1,rule:{id:`public-debug-artifact`,title:`Public debug artifact`,severity:`warn`,recommendation:`Remove debug artifacts from public output; logs and dumps often reveal source paths, internal routes, tokens, or environment snapshots.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/public-env-secret-name`,id:`public-env-secret-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`public-env-secret-name`,title:`Secret-like public env variable`,severity:`warn`,recommendation:`Public env prefixes are inlined into browser bundles. Rename public values to non-secret names, and keep tokens, passwords, private keys, and service-role credentials server-only.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/query-destructure-result`,id:`query-destructure-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-destructure-result`,title:`Spreading a whole query result subscribes to every field`,severity:`warn`,recommendation:"TanStack Query only subscribes to the fields you actually read, so `query.data` is as targeted as destructuring. Spreading the whole result reads every field and re-renders on each change; pick out only the fields you need.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-floating-mutate-async`,id:`query-floating-mutate-async`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-floating-mutate-async`,title:`Floating mutateAsync rejection`,severity:`warn`,recommendation:"Await, return, or handle rejection from the `mutateAsync()` promise so a failed mutation cannot become an unhandled rejection.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-mutation-missing-invalidation`,id:`query-mutation-missing-invalidation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-mutation-missing-invalidation`,title:`Mutation without cache invalidation`,severity:`warn`,recommendation:"Add `onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] })` so cached data stays in sync after the mutation",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-mutation-in-effect-as-read`,id:`query-no-mutation-in-effect-as-read`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-mutation-in-effect-as-read`,title:`Mutation driven from an effect as a read`,severity:`warn`,recommendation:"Use `useQuery` with a `queryKey` and `enabled` for reads started by an effect so the result is cached and deduplicated.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-query-in-effect`,id:`query-no-query-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-query-in-effect`,title:`Query refetch inside useEffect`,severity:`warn`,recommendation:"Use `queryKey` changes or `enabled` so React Query schedules the fetch once instead of refetching again from `useEffect`.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-rest-destructuring`,id:`query-no-rest-destructuring`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-rest-destructuring`,title:`Rest destructuring on query result`,severity:`warn`,recommendation:"Destructure only the fields you need, like `const { data, isLoading } = useQuery(...)`. Rest destructuring subscribes to every field and adds re-renders.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-usequery-for-mutation`,id:`query-no-usequery-for-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-usequery-for-mutation`,title:`useQuery used for mutation`,severity:`warn`,recommendation:"Use `useMutation()` for POST/PUT/DELETE. It gives onSuccess/onError callbacks, doesn't auto-refetch, and models writes correctly.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-void-query-fn`,id:`query-no-void-query-fn`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-void-query-fn`,title:`queryFn returns no value`,severity:`warn`,recommendation:"queryFn must return a value for the cache. Use the `enabled` option to conditionally disable the query instead of returning undefined",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-stable-query-client`,id:`query-stable-query-client`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-stable-query-client`,title:`Unstable QueryClient in component`,severity:`warn`,recommendation:"Move `new QueryClient()` to module scope, or wrap it in `useState(() => new QueryClient())`. Recreating it each render wipes the cache.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/r3f-cap-device-pixel-ratio`,id:`r3f-cap-device-pixel-ratio`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-cap-device-pixel-ratio`,title:`Unbounded device pixel ratio`,severity:`warn`,recommendation:`Cap devicePixelRatio, commonly at 2, or pass an explicit DPR range so high-density displays do not multiply rendering work without a bound`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-limit-shadowed-point-lights`,id:`r3f-limit-shadowed-point-lights`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-limit-shadowed-point-lights`,title:`Too many shadow-casting point lights`,severity:`warn`,recommendation:`Keep at most two shadow-casting point lights in one scene, or replace them with cheaper directional, spot, baked, or fake shadows`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-advancing-clock-in-use-frame`,id:`r3f-no-advancing-clock-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-advancing-clock-in-use-frame`,title:`Clock advanced inside useFrame`,severity:`warn`,recommendation:`Use the delta argument supplied to useFrame or read clock.elapsedTime without advancing the shared clock`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],disabledWhen:[`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-allocation-in-pointer-move`,id:`r3f-no-allocation-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-allocation-in-pointer-move`,title:`Allocation inside an R3F pointer-move handler`,severity:`warn`,recommendation:`Reuse component-owned vectors and Three.js objects while handling pointer movement`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-async-use-frame`,id:`r3f-no-async-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-async-use-frame`,title:`Async useFrame callback`,severity:`warn`,recommendation:`Keep useFrame synchronous; start asynchronous work outside the render loop and consume its completed state from the callback`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-clone-in-use-frame`,id:`r3f-no-clone-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-clone-in-use-frame`,title:`Three.js clone inside useFrame`,severity:`warn`,recommendation:`Clone once outside the frame loop or reuse a scratch vector, quaternion, matrix, or object allocated with useMemo or useRef`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-deep-use-three-selector`,id:`r3f-no-deep-use-three-selector`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-deep-use-three-selector`,title:`useThree selector reads a mutable Three.js field`,severity:`warn`,recommendation:`Select the stable R3F store object, then read its mutable Three.js fields where they are consumed`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`,`r3f:6`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-dispose-loader-cache`,id:`r3f-no-dispose-loader-cache`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-dispose-loader-cache`,title:`Disposal of a cached R3F loader asset`,severity:`warn`,recommendation:`Do not dispose assets returned by cached R3F and Drei loader hooks`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-duplicate-primitive-object`,id:`r3f-no-duplicate-primitive-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-duplicate-primitive-object`,title:`Primitive object mounted twice`,severity:`warn`,recommendation:`Mount a Three.js object through one <primitive>, or clone it deliberately when two independent instances are required`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-extend-in-render`,id:`r3f-no-extend-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-extend-in-render`,title:`R3F catalogue extension during render`,severity:`warn`,recommendation:`Call extend at module scope so React renders and Strict Mode replays do not repeatedly mutate R3F's global catalogue`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-extend-three-namespace`,id:`r3f-no-extend-three-namespace`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-extend-three-namespace`,title:`Whole Three.js namespace registered with R3F`,severity:`warn`,recommendation:`Pass extend an object containing only the Three.js constructors used by JSX so bundlers can tree-shake the rest of the namespace`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-fresh-portal-container`,id:`r3f-no-fresh-portal-container`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-fresh-portal-container`,title:`Fresh R3F portal container`,severity:`warn`,recommendation:`Keep the createPortal container stable with module scope, lazy state, or useMemo so R3F can preserve the portal store and event layer`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-fresh-use-three-selector`,id:`r3f-no-fresh-use-three-selector`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-fresh-use-three-selector`,title:`Fresh useThree selector result`,severity:`warn`,recommendation:`Select one stable store field at a time, or provide an equality function when returning an object or array from useThree`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`,`r3f:6`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-imperative-attach-of-managed-ref`,id:`r3f-no-imperative-attach-of-managed-ref`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-imperative-attach-of-managed-ref`,title:`Imperative attachment of an R3F-managed ref`,severity:`error`,recommendation:`Let R3F attach objects referenced by JSX. Do not add or attach the same ref.current imperatively`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-inline-primitive-object`,id:`r3f-no-inline-primitive-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-inline-primitive-object`,title:`Inline primitive object`,severity:`warn`,recommendation:`Create or clone the Three.js object once outside render, or memoize it, before passing it to <primitive>`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-inline-resource-prop`,id:`r3f-no-inline-resource-prop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-inline-resource-prop`,title:`Inline R3F resource prop`,severity:`warn`,recommendation:`Create the Three.js geometry or material once at module scope, in lazy state, or with useMemo before passing it as an R3F resource prop`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-internal-imports`,id:`r3f-no-internal-imports`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-internal-imports`,title:`Private R3F import`,severity:`warn`,recommendation:`Import from @react-three/fiber or one of its documented public entry points instead of a dist or src implementation path`,category:`Maintainability`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-manual-canvas-resize`,id:`r3f-no-manual-canvas-resize`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-manual-canvas-resize`,title:`Manual resize loop for an R3F-owned renderer`,severity:`warn`,recommendation:`Let Canvas and its ResizeObserver own renderer sizing instead of registering a window resize loop`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-mutate-loader-cache`,id:`r3f-no-mutate-loader-cache`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-mutate-loader-cache`,title:`Mutation of a cached R3F loader asset`,severity:`warn`,recommendation:`Clone the loaded object or resource before mutating it`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-mutating-pointer-event-data`,id:`r3f-no-mutating-pointer-event-data`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-mutating-pointer-event-data`,title:`Mutation of shared R3F pointer-event data`,severity:`warn`,recommendation:`Copy shared pointer-event vectors before applying local-space transforms or other mutations`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-new-in-use-frame`,id:`r3f-no-new-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-new-in-use-frame`,title:`Allocation inside useFrame`,severity:`warn`,recommendation:`Allocate Three.js objects once in module scope, useMemo, or useRef and mutate the reusable instance inside useFrame`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-null-loader-input`,id:`r3f-no-null-loader-input`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-null-loader-input`,title:`Nullish R3F loader input`,severity:`warn`,recommendation:`Conditionally render a child component that calls the loader hook only after it has a real asset URL`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-object-pointer-capture`,id:`r3f-no-object-pointer-capture`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-object-pointer-capture`,title:`Pointer capture called on an R3F scene object`,severity:`error`,recommendation:`Call pointer-capture methods on the R3F event target or currentTarget, not object or eventObject`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-recursive-raf-with-use-frame`,id:`r3f-no-recursive-raf-with-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-recursive-raf-with-use-frame`,title:`Competing animation loop alongside useFrame`,severity:`warn`,recommendation:`Use R3F's useFrame subscription as the component's single animation loop instead of starting a second browser or renderer loop`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-state-in-pointer-move`,id:`r3f-no-state-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-state-in-pointer-move`,title:`React state update inside an R3F pointer-move handler`,severity:`warn`,recommendation:`Keep pointer-move previews in Three.js refs or transient state and publish one semantic React update when the interaction commits`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-state-in-use-frame`,id:`r3f-no-state-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-state-in-use-frame`,title:`React state update inside useFrame`,severity:`warn`,recommendation:`Mutate Three.js refs or an external transient store inside useFrame; reserve React state for guarded, infrequent transitions`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-sync-readback-in-use-frame`,id:`r3f-no-sync-readback-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-sync-readback-in-use-frame`,title:`Synchronous readback inside useFrame`,severity:`warn`,recommendation:`Move readback to a discrete or serialized async path and reuse the latest completed result during frames`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-unstable-args`,id:`r3f-no-unstable-args`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-unstable-args`,title:`Unstable R3F constructor args`,severity:`warn`,recommendation:`Keep reference-valued constructor arguments stable with module scope or useMemo so React Three Fiber does not reconstruct and dispose the Three.js object on a later render`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-use-frame-dependency-array`,id:`r3f-no-use-frame-dependency-array`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-use-frame-dependency-array`,title:`Dependency array passed to useFrame`,severity:`warn`,recommendation:`Remove the dependency array. Pass a numeric render priority on R3F v9, or supported scheduling options on R3F v10`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:3`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-prefer-instanced-mesh`,id:`r3f-prefer-instanced-mesh`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-prefer-instanced-mesh`,title:`Repeated R3F meshes use separate draw calls`,severity:`warn`,recommendation:`Render repeated meshes that share geometry and material with <instancedMesh> so they can use one draw call`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-prefer-use-loader`,id:`r3f-prefer-use-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-prefer-use-loader`,title:`Imperative Three.js loader in a React effect`,severity:`warn`,recommendation:`Load render resources with R3F useLoader so Suspense caching can share and deduplicate them`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`,`r3f:3`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-frame-delta`,id:`r3f-require-frame-delta`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-frame-delta`,title:`Frame-rate-dependent animation`,severity:`warn`,recommendation:`Scale incremental transforms and interpolation by useFrame delta, use delta-aware damping, or assign from absolute time`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-global-effect-cleanup`,id:`r3f-require-global-effect-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-global-effect-cleanup`,title:`Unreleased global R3F render-loop effect`,severity:`warn`,recommendation:`Register addEffect, addAfterEffect, and addTail from an effect and return the exact disposer so remounts do not retain global render-loop callbacks`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-instanced-buffer-update`,id:`r3f-require-instanced-buffer-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-instanced-buffer-update`,title:`Instanced mesh buffer is not marked for upload`,severity:`error`,recommendation:`After setMatrixAt or setColorAt, set the matching instance buffer's needsUpdate flag to true`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-owned-texture-cleanup`,id:`r3f-require-owned-texture-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-owned-texture-cleanup`,title:`Locally owned Three.js texture is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed textures in a React effect cleanup`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-projection-matrix-update`,id:`r3f-require-projection-matrix-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-projection-matrix-update`,title:`Missing camera projection-matrix update`,severity:`error`,recommendation:`Call camera.updateProjectionMatrix() after imperatively changing projection properties so Three.js renders the new frustum`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-render-with-positive-priority`,id:`r3f-require-render-with-positive-priority`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-render-with-positive-priority`,title:`Positive useFrame priority without a render`,severity:`warn`,recommendation:`Render the scene or composer from the positive-priority useFrame subscription, or use a non-positive priority to keep automatic rendering enabled`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-root-unmount`,id:`r3f-require-root-unmount`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-root-unmount`,title:`R3F root without unmount cleanup`,severity:`warn`,recommendation:`Unmount component-owned R3F roots in React cleanup, or return an exact disposer that unmounts the root`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-canvas-prop-compatibility`,id:`r3f-webgpu-canvas-prop-compatibility`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-canvas-prop-compatibility`,title:`Incompatible R3F Canvas renderer prop`,severity:`error`,recommendation:`Use renderer with @react-three/fiber/webgpu, gl with /legacy, and never pass both renderer APIs to one Canvas`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-gl-state`,id:`r3f-webgpu-no-gl-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-gl-state`,title:`WebGPU root reads deprecated gl state`,severity:`warn`,recommendation:`Read state.renderer from the R3F WebGPU root instead of state.gl`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-js-uniform-branch`,id:`r3f-webgpu-no-js-uniform-branch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-js-uniform-branch`,title:`JavaScript branch reads a TSL uniform value`,severity:`warn`,recommendation:`Express uniform-dependent control flow with TSL If, select, or Loop nodes so it runs on the GPU`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-legacy-effect-composer`,id:`r3f-webgpu-no-legacy-effect-composer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-legacy-effect-composer`,title:`Legacy EffectComposer inside an R3F WebGPU Canvas`,severity:`error`,recommendation:`Use the renderer's node-based post-processing pipeline instead of @react-three/postprocessing EffectComposer`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-legacy-material-api`,id:`r3f-webgpu-no-legacy-material-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-legacy-material-api`,title:`Legacy shader material API inside an R3F WebGPU Canvas`,severity:`error`,recommendation:`Use Three.js node materials and TSL for custom shaders rendered by the WebGPU backend`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-unregistered-pipeline-pass`,id:`r3f-webgpu-no-unregistered-pipeline-pass`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-unregistered-pipeline-pass`,title:`Unregistered WebGPU pipeline pass`,severity:`error`,recommendation:`Return custom passes from the WebGPU post-processing callback so R3F registers them in state.passes`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/radio-input-missing-name`,id:`radio-input-missing-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`radio-input-missing-name`,title:`Radio input missing name`,severity:`warn`,recommendation:'Give every radio in the same group the same `name` prop (e.g. `<input type="radio" name="shippingSpeed" />`). The browser groups radios and enables arrow-key navigation only when they share a `name`.',category:`Accessibility`,framework:`global`,isScanRule:!1}},{key:`react-doctor/raw-sql-injection-risk`,id:`raw-sql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`raw-sql-injection-risk`,title:`Raw SQL built outside parameter binding`,severity:`warn`,recommendation:`Keep user input in driver parameters or ORM bind variables. Avoid unsafe/raw SQL helpers and string interpolation for queries.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/react-compiler-no-manual-memoization`,id:`react-compiler-no-manual-memoization`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-compiler-no-manual-memoization`,title:`Manual memoization in compiler-managed code`,severity:`warn`,recommendation:"Profile compiler-managed code and remove `useMemo`, `useCallback`, or `memo` only when the manual cache no longer carries behavioral or performance intent.",category:`Maintainability`,framework:`global`,requires:[`react-compiler`],isScanRule:!1}},{key:`react-doctor/react-in-jsx-scope`,id:`react-in-jsx-scope`,source:`react-doctor`,originallyExternal:!0,rule:{id:`react-in-jsx-scope`,title:`React not in scope for JSX`,severity:`warn`,recommendation:"If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import `React` at the top of the file.",category:`Bugs`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/react-markdown-unsanitized-raw-html`,id:`react-markdown-unsanitized-raw-html`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-markdown-unsanitized-raw-html`,title:`Unsanitized raw HTML in React Markdown`,severity:`warn`,recommendation:"Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/react-router-csp-nonce-consistency`,id:`react-router-csp-nonce-consistency`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-csp-nonce-consistency`,title:`CSP nonce is not shared across server rendering`,severity:`error`,recommendation:`Pass the same request-scoped nonce to ServerRouter and the React server-rendering stream options.`,category:`Security`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-descendant-routes-require-splat`,id:`react-router-descendant-routes-require-splat`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-descendant-routes-require-splat`,title:`Parent path cannot match descendant routes`,severity:`error`,recommendation:`End the mounting route path with /* so its descendant Routes tree can match deeper URLs.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-guard-aborted-handle-error`,id:`react-router-guard-aborted-handle-error`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-guard-aborted-handle-error`,title:`Aborted requests are reported as errors`,severity:`warn`,recommendation:`Return early when request.signal.aborted before reporting the error from handleError.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-internal-route-anchor`,id:`react-router-internal-route-anchor`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-internal-route-anchor`,title:`Internal route uses a document navigation`,severity:`warn`,recommendation:`Use React Router Link for navigation to a known UI route.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-loader-fetch-forwards-signal`,id:`react-router-loader-fetch-forwards-signal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-loader-fetch-forwards-signal`,title:`Loader fetch ignores cancellation`,severity:`warn`,recommendation:`Pass request.signal to fetch so superseded navigations cancel work that no longer contributes to the route.`,category:`Performance`,framework:`global`,requires:[`react`,`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-loader-parallel-fetch`,id:`react-router-loader-parallel-fetch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-loader-parallel-fetch`,title:`Independent loader work runs sequentially`,severity:`warn`,recommendation:`Start independent loader work together with Promise.all to avoid adding serial latency to navigation.`,category:`Performance`,framework:`global`,requires:[`react`,`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-nested-route-requires-outlet`,id:`react-router-nested-route-requires-outlet`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-nested-route-requires-outlet`,title:`Nested routes have no Outlet`,severity:`error`,recommendation:`Render <Outlet /> from the parent route component where child routes belong.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-catch-middleware-next`,id:`react-router-no-catch-middleware-next`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-catch-middleware-next`,title:`Middleware catch cannot observe downstream errors`,severity:`warn`,recommendation:`Use an ErrorBoundary, handleError, or middleware response instrumentation instead of catching next().`,category:`Bugs`,framework:`global`,requires:[`react-router:7.8`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-client-module-in-server-render`,id:`react-router-no-client-module-in-server-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-client-module-in-server-render`,title:`Client-only module rendered on the server`,severity:`error`,recommendation:`Move client-only behavior behind a hydration boundary instead of importing a .client module into server-rendered JSX.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-no-duplicate-route-id`,id:`react-router-no-duplicate-route-id`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-duplicate-route-id`,title:`Duplicate route ID`,severity:`error`,recommendation:`Give every explicit route ID a unique value within the router.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-empty-leaf-route`,id:`react-router-no-empty-leaf-route`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-empty-leaf-route`,title:`Leaf route renders nothing`,severity:`warn`,recommendation:`Add element, Component, or lazy to a UI leaf route, or remove the empty route.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-invalid-absolute-child-path`,id:`react-router-no-invalid-absolute-child-path`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-invalid-absolute-child-path`,title:`Absolute child path escapes its parent`,severity:`error`,recommendation:`Make the child path relative, or prefix its absolute path with the complete parent route path.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-invalid-lazy-route-properties`,id:`react-router-no-invalid-lazy-route-properties`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-invalid-lazy-route-properties`,title:`Immutable route property returned from lazy`,severity:`error`,recommendation:`Keep route-matching properties on the static route object; lazy may return only non-matching route properties.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.9`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-invalid-splat-path`,id:`react-router-no-invalid-splat-path`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-invalid-splat-path`,title:`Invalid splat route path`,severity:`error`,recommendation:`Use '*' for a catch-all route or end a path with '/*'.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-loader-request-body`,id:`react-router-no-loader-request-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-loader-request-body`,title:`Loader reads a request body`,severity:`error`,recommendation:`Read submitted request bodies in an action; loaders handle GET requests and should use URL search parameters instead.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-middleware-response-body-consumption`,id:`react-router-no-middleware-response-body-consumption`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-middleware-response-body-consumption`,title:`Middleware consumes the outgoing response body`,severity:`warn`,recommendation:`Inspect or change response headers and status without consuming the body returned by next().`,category:`Bugs`,framework:`global`,requires:[`react-router:7.9`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-multiple-blockers`,id:`react-router-no-multiple-blockers`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-multiple-blockers`,title:`Multiple blockers in one component`,severity:`error`,recommendation:`Combine blocking conditions into one useBlocker call per rendered component.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.7`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-multiple-middleware-next`,id:`react-router-no-multiple-middleware-next`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-multiple-middleware-next`,title:`Middleware continuation called twice`,severity:`error`,recommendation:`Call next exactly once and reuse the returned Response.`,category:`Bugs`,framework:`global`,requires:[`react-router:7.9`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-multiple-set-search-params-in-tick`,id:`react-router-no-multiple-set-search-params-in-tick`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-multiple-set-search-params-in-tick`,title:`Search params updated multiple times`,severity:`warn`,recommendation:`Combine changes into one setSearchParams call because updates in the same tick do not queue like React state.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-navigate-in-render`,id:`react-router-no-navigate-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-navigate-in-render`,title:`navigate() called during render`,severity:`error`,recommendation:`Render <Navigate> for declarative redirects, redirect from a loader or action, or navigate from an event or effect.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-nested-router`,id:`react-router-no-nested-router`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-nested-router`,title:`Router nested inside another router`,severity:`error`,recommendation:`Keep one router provider at the application root.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-no-redirect-in-try-catch`,id:`react-router-no-redirect-in-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-redirect-in-try-catch`,title:`redirect() inside try-catch`,severity:`error`,recommendation:`Move redirect outside the guarded try block or rethrow redirect responses from catch.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-route-module-environment-suffix`,id:`react-router-no-route-module-environment-suffix`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-route-module-environment-suffix`,title:`Route module has an environment suffix`,severity:`error`,recommendation:`Keep the route module environment-neutral and move environment-specific code into imported .client or .server modules.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-no-router-in-render`,id:`react-router-no-router-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-router-in-render`,title:`Router created during render`,severity:`error`,recommendation:`Create the router at module scope so navigation state and subscriptions survive renders.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-session-mutation-in-loader`,id:`react-router-no-session-mutation-in-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-session-mutation-in-loader`,title:`Loader mutates session state`,severity:`error`,recommendation:`Mutate and commit sessions in an action, then redirect to a loader.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-static-cookie-expires`,id:`react-router-no-static-cookie-expires`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-static-cookie-expires`,title:`Cookie expiry is fixed at module load`,severity:`error`,recommendation:`Use maxAge for a duration-based cookie lifetime.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-unsynchronized-search-params-mutation`,id:`react-router-no-unsynchronized-search-params-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-unsynchronized-search-params-mutation`,title:`Search params mutated without navigation`,severity:`error`,recommendation:`Clone the URLSearchParams value, mutate the clone, and return or pass it to setSearchParams.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-use-loader-data-in-error-ui`,id:`react-router-no-use-loader-data-in-error-ui`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-use-loader-data-in-error-ui`,title:`Error UI assumes loader data exists`,severity:`error`,recommendation:`Use useRouteLoaderData with an explicit route ID and handle undefined inside error UI.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-prefer-route-lazy`,id:`react-router-prefer-route-lazy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-prefer-route-lazy`,title:`React.lazy creates a route-module waterfall`,severity:`warn`,recommendation:`Load route modules with the route lazy property so initial navigation does not download every route component.`,category:`Performance`,framework:`global`,requires:[`react`,`react-router:6.9`],disabledWhen:[`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-require-root-error-boundary`,id:`react-router-require-root-error-boundary`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-require-root-error-boundary`,title:`Root route lacks an error boundary`,severity:`error`,recommendation:`Define ErrorBoundary or errorElement on the root route so rendering, loader, and action failures have an application fallback.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-resource-link-requires-reload`,id:`react-router-resource-link-requires-reload`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-resource-link-requires-reload`,title:`Resource link intercepted as navigation`,severity:`error`,recommendation:`Add reloadDocument to resource links so the browser downloads or opens the resource instead of client-routing it.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-return-navigation-promise-in-transition`,id:`react-router-return-navigation-promise-in-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-return-navigation-promise-in-transition`,title:`Transition drops a navigation promise`,severity:`warn`,recommendation:`Return or await the navigation promise from the transition callback so pending state lasts through navigation.`,category:`Bugs`,framework:`global`,requires:[`react-router:7.10`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-server-middleware-return-response`,id:`react-router-server-middleware-return-response`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-server-middleware-return-response`,title:`Server middleware drops the Response`,severity:`error`,recommendation:`Return the Response produced by next(), or return an explicit replacement Response.`,category:`Bugs`,framework:`global`,requires:[`react-router:7.9`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-session-mutation-requires-commit`,id:`react-router-session-mutation-requires-commit`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-session-mutation-requires-commit`,title:`Session mutation is not committed`,severity:`error`,recommendation:`Serialize the session with commitSession or destroySession and include its Set-Cookie value in the returned Response.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-v8-no-meta-data-field`,id:`react-router-v8-no-meta-data-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-v8-no-meta-data-field`,title:`Removed meta data field`,severity:`error`,recommendation:`Read loaderData instead of the removed data field.`,category:`Maintainability`,framework:`global`,requires:[`react-router:8`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-v8-no-react-router-dom-import`,id:`react-router-v8-no-react-router-dom-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-v8-no-react-router-dom-import`,title:`Removed react-router-dom import`,severity:`error`,recommendation:`Import DOM-only APIs from react-router/dom and all other APIs from react-router.`,category:`Maintainability`,framework:`global`,requires:[`react-router:8`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-v8-no-removed-future-flags`,id:`react-router-v8-no-removed-future-flags`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-v8-no-removed-future-flags`,title:`Removed React Router future flag`,severity:`error`,recommendation:`Remove v8 future flags; move v8_splitRouteModules to the top-level splitRouteModules option.`,category:`Maintainability`,framework:`global`,requires:[`react-router:8`,`react-router-framework`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-valid-route-object`,id:`react-router-valid-route-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-valid-route-object`,title:`Contradictory route object`,severity:`error`,recommendation:`Remove incompatible route properties so each route has one unambiguous rendering contract.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/redux-useselector-inline-derivation`,id:`redux-useselector-inline-derivation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`redux-useselector-inline-derivation`,title:`useSelector derives data inline`,severity:`warn`,recommendation:`Select the raw slice and memoize derivation so Redux actions do not rebuild a collection and redraw this component.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/redux-useselector-returns-new-collection`,id:`redux-useselector-returns-new-collection`,source:`react-doctor`,originallyExternal:!1,rule:{id:`redux-useselector-returns-new-collection`,title:`useSelector returns a new collection`,severity:`warn`,recommendation:"Return a stable selected value, split selectors, or pass `shallowEqual` so every Redux action does not redraw this component.",category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/remotion-calculate-metadata-fetch-signal`,id:`remotion-calculate-metadata-fetch-signal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-calculate-metadata-fetch-signal`,title:`calculateMetadata fetch ignores abortSignal`,severity:`error`,recommendation:"Destructure `abortSignal` from the calculateMetadata argument and pass it to direct fetch calls as `{signal: abortSignal}`.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-deterministic-randomness`,id:`remotion-deterministic-randomness`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-deterministic-randomness`,title:`Randomness changes between rendered frames`,severity:`error`,recommendation:"Use Remotion's seeded `random(seed)` helper so the same frame produces the same value in every render tab.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-css-animation`,id:`remotion-no-css-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-css-animation`,title:`CSS animation is not frame-driven`,severity:`error`,recommendation:"Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-css-transition`,id:`remotion-no-css-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-css-transition`,title:`CSS transition is not frame-driven`,severity:`error`,recommendation:"Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-css-url-assets`,id:`remotion-no-css-url-assets`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-css-url-assets`,title:`CSS URL asset can flicker in Remotion`,severity:`error`,recommendation:"Render the asset with `Img` inside an `AbsoluteFill`, or preload the same source with a hidden `Img` when a CSS mask is required.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-module-scope-delay-render`,id:`remotion-no-module-scope-delay-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-module-scope-delay-render`,title:`Module-scoped delayRender blocks every composition`,severity:`error`,recommendation:"Create the handle once inside the component. Use `useDelayRender()` on Remotion 4.0.342 or newer, or lazy `useState(() => delayRender())` on earlier versions.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],isScanRule:!1}},{key:`react-doctor/remotion-no-native-media-elements`,id:`remotion-no-native-media-elements`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-native-media-elements`,title:`Native media element bypasses Remotion loading`,severity:`error`,recommendation:`Use Remotion's media components so rendering waits for assets and seeks media to the requested frame.`,category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-next-image`,id:`remotion-no-next-image`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-next-image`,title:`Next.js Image can flicker in Remotion`,severity:`error`,recommendation:"Use `Img` from `remotion`, which delays rendering until the image is loaded.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-stable-delay-render-handle`,id:`remotion-stable-delay-render-handle`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-stable-delay-render-handle`,title:`delayRender handle is recreated during render`,severity:`error`,recommendation:"Prefer `useDelayRender()`, or initialize `delayRender()` once with `useState(() => delayRender())`.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/rendering-animate-svg-wrapper`,id:`rendering-animate-svg-wrapper`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-animate-svg-wrapper`,title:`Animating an SVG directly`,severity:`warn`,recommendation:`Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-conditional-render`,id:`rendering-conditional-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-conditional-render`,title:`Number before && renders stray 0`,severity:`warn`,recommendation:"Use `{items.length > 0 && <List />}`, or a ternary like `{items.length ? <List /> : null}`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/rendering-hoist-jsx`,id:`rendering-hoist-jsx`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-hoist-jsx`,title:`Constant JSX rebuilt each render`,severity:`warn`,recommendation:"Move the static JSX out to the top of the file: `const ICON = <svg>...</svg>`, so it isn't rebuilt on every render",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-hydration-mismatch-time`,id:`rendering-hydration-mismatch-time`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-hydration-mismatch-time`,title:`Time or random value in JSX`,severity:`warn`,recommendation:`Move time or random values into useEffect+useState so they only run in the browser, or add suppressHydrationWarning to the parent if it's intentional`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/rendering-hydration-no-flicker`,id:`rendering-hydration-no-flicker`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-hydration-no-flicker`,title:`useEffect setState flashes on mount`,severity:`warn`,recommendation:"Initialize state from a render-safe value before the first paint, or read external mutable values with `useSyncExternalStore`.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-script-defer-async`,id:`rendering-script-defer-async`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-script-defer-async`,title:`Script without defer or async`,severity:`warn`,recommendation:'Add `defer` for scripts that need the page, or `async` for standalone ones like analytics. In Next.js, use `<Script strategy="afterInteractive" />`',category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-svg-precision`,id:`rendering-svg-precision`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-svg-precision`,title:`Overly precise SVG path values`,severity:`warn`,recommendation:`Round path, points, and transform decimals to 1 or 2 digits. The extra precision adds bytes with no visible difference.`,category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-usetransition-loading`,id:`rendering-usetransition-loading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-usetransition-loading`,title:`Loading useState forces extra render`,severity:`warn`,recommendation:"Replace with `const [isPending, startTransition] = useTransition()`, which marks the update as non-urgent and interruptible so the input stays responsive",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/repository-secret-file`,id:`repository-secret-file`,source:`react-doctor`,originallyExternal:!1,rule:{id:`repository-secret-file`,title:`Secret file checked into repository`,severity:`error`,recommendation:`Remove committed env files, service-account credentials, npm auth tokens, and webhook URLs; rotate exposed values and keep only redacted examples in source.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/request-body-mass-assignment`,id:`request-body-mass-assignment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`request-body-mass-assignment`,title:`Request input spread without field allowlist`,severity:`warn`,recommendation:"Assign explicit, allowlisted fields (or validate with a strict schema and no `.passthrough()`) instead of spreading/merging request input. Otherwise the client can set ownership, role, or price columns (mass assignment) or pollute the prototype.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/require-autoplay-video-poster`,id:`require-autoplay-video-poster`,source:`react-doctor`,originallyExternal:!1,rule:{id:`require-autoplay-video-poster`,title:`Autoplay video has no poster frame`,severity:`warn`,recommendation:"Provide a representative `poster` image so the video region is intentional before playback begins and while media is unavailable.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/require-render-return`,id:`require-render-return`,source:`react-doctor`,originallyExternal:!0,rule:{id:`require-render-return`,title:`Render method does not return`,severity:`error`,recommendation:"Return JSX or `null` from `render` so the component intentionally shows something or nothing.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/require-scale-reveal-transform-origin`,id:`require-scale-reveal-transform-origin`,source:`react-doctor`,originallyExternal:!1,rule:{id:`require-scale-reveal-transform-origin`,title:`Scaled menu omits its transform origin`,severity:`warn`,recommendation:`Anchor scaled menus and listboxes to the edge nearest their trigger.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/rerender-defer-reads-hook`,id:`rerender-defer-reads-hook`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-defer-reads-hook`,title:`URL hook value only read in handlers`,severity:`warn`,recommendation:"Read the URL inside the handler (e.g. `new URL(window.location.href).searchParams`) so the component doesn't redraw every time the URL changes.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-dependencies`,id:`rerender-dependencies`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-dependencies`,title:`Unstable value recreated every render`,severity:`error`,recommendation:`Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.`,category:`Bugs`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-derived-state-from-hook`,id:`rerender-derived-state-from-hook`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-derived-state-from-hook`,title:`Continuous hook value over-renders`,severity:`warn`,recommendation:'Use a threshold hook like `useMediaQuery("(max-width: 767px)")`, so the screen only redraws when the answer changes, not on every pixel',category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-functional-setstate`,id:`rerender-functional-setstate`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-functional-setstate`,title:`setState reads a stale value`,severity:`warn`,recommendation:"Use the callback form: `setState(prev => prev + 1)` to always read the latest value",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-lazy-ref-init`,id:`rerender-lazy-ref-init`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-lazy-ref-init`,title:`Ref initializer runs on every render`,severity:`warn`,recommendation:`Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-lazy-state-init`,id:`rerender-lazy-state-init`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-lazy-state-init`,title:`State initializer runs on every render`,severity:`warn`,recommendation:`Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-memo-before-early-return`,id:`rerender-memo-before-early-return`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-memo-before-early-return`,title:`useMemo before an early return`,severity:`warn`,recommendation:`Move the JSX into a child component rendered after the early return, so renders that take the early return never build it`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-memo-with-default-value`,id:`rerender-memo-with-default-value`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-memo-with-default-value`,title:`Empty default prop breaks memo`,severity:`warn`,recommendation:"Move it to the top of the file: `const EMPTY_ITEMS: Item[] = []`, then use that as the default value",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-state-only-in-handlers`,id:`rerender-state-only-in-handlers`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-state-only-in-handlers`,title:`State only used in handlers`,severity:`warn`,recommendation:"Use useRef instead of useState when the value is only set and never shown on screen. `ref.current = ...` updates it without redrawing the component.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-transitions-scroll`,id:`rerender-transitions-scroll`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-transitions-scroll`,title:`setState in a scroll handler`,severity:`warn`,recommendation:`Wrap the setState in startTransition, use useDeferredValue, or keep the value in a ref and throttle with requestAnimationFrame, so these events don't redraw the screen every time`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-animate-layout-property`,id:`rn-animate-layout-property`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-animate-layout-property`,title:`Animating a layout property`,severity:`warn`,recommendation:"Retired: Reanimated `useAnimatedStyle` can safely drive layout-affecting properties on the UI thread, so this pattern should not be flagged.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/rn-animation-reaction-as-derived`,id:`rn-animation-reaction-as-derived`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-animation-reaction-as-derived`,title:`useAnimatedReaction just copies a value`,severity:`warn`,recommendation:"This useAnimatedReaction just copies one value to another. Replace it with `useDerivedValue(() => ..., [deps])`, which is shorter and tracks changes for you.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-no-ignored-scroll-prop`,id:`rn-bottom-sheet-no-ignored-scroll-prop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-no-ignored-scroll-prop`,title:`Ignored BottomSheetScrollView prop`,severity:`warn`,recommendation:`Remove scrollEventThrottle, decelerationRate, and onScrollBeginDrag from BottomSheetScrollView because the component ignores them.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-no-state-in-on-animate`,id:`rn-bottom-sheet-no-state-in-on-animate`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-no-state-in-on-animate`,title:`React state update in Bottom Sheet onAnimate`,severity:`warn`,recommendation:`Avoid starting React renders from onAnimate. Use animatedIndex or animatedPosition for animation-coupled UI, or onChange for committed index state.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-prefer-native`,id:`rn-bottom-sheet-prefer-native`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-prefer-native`,title:`JS bottom sheet misses native sheet behavior`,severity:`warn`,recommendation:'When native presentation fits the design, use `<Modal presentationStyle="formSheet">` for platform-native gestures, accessibility, and presentation behavior.',category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-use-integrated-scrollable`,id:`rn-bottom-sheet-use-integrated-scrollable`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-use-integrated-scrollable`,title:`React Native scrollable inside a Bottom Sheet`,severity:`warn`,recommendation:`Use @gorhom/bottom-sheet's integrated BottomSheetScrollView, BottomSheetFlatList, BottomSheetSectionList, or BottomSheetVirtualizedList so gestures coordinate with the sheet.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-detox-missing-await`,id:`rn-detox-missing-await`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-detox-missing-await`,title:`Un-awaited Detox action`,severity:`warn`,recommendation:"Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-list-callback-per-row`,id:`rn-list-callback-per-row`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-callback-per-row`,title:`Inline handler in list renderItem`,severity:`warn`,recommendation:`Move the handler out with useCallback at list scope and pass the row id as a prop. Then memo() rows skip redrawing when their data has not changed.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-list-data-mapped`,id:`rn-list-data-mapped`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-data-mapped`,title:`List data rebuilt every render`,severity:`warn`,recommendation:"This builds a new array each time the parent redraws, so every row redraws too. Wrap it in `useMemo(() => items.map(...), [items])` to keep the same array.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-list-missing-estimated-item-size`,id:`rn-list-missing-estimated-item-size`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-missing-estimated-item-size`,title:`List missing estimatedItemSize`,severity:`warn`,recommendation:"Without `estimatedItemSize` the list guesses row height and can flash blank cells on fast scroll. Add `estimatedItemSize={<avg-row-height-in-px>}` so it matches your rows.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-list-recyclable-without-types`,id:`rn-list-recyclable-without-types`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-recyclable-without-types`,title:`Recyclable list missing getItemType`,severity:`warn`,recommendation:"When rows have different shapes, reused cells can show the wrong layout. Add `getItemType` that returns a stable type for each row shape so FlashList keeps separate recycling pools.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-deep-imports`,id:`rn-no-deep-imports`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-deep-imports`,title:`Deep import into react-native internals`,severity:`warn`,recommendation:"Import the symbol from `react-native` (the package root) instead of the deprecated `react-native/Libraries/...` subpath, which RFC 0894 removes on upgrade.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-deprecated-modules`,id:`rn-no-deprecated-modules`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-deprecated-modules`,title:`Module removed from react-native core`,severity:`error`,recommendation:`These modules were removed from react-native core. Import them from the community package instead.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-dimensions-get`,id:`rn-no-dimensions-get`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-dimensions-get`,title:`Dimensions.get over useWindowDimensions`,severity:`warn`,recommendation:"Use `const { width, height } = useWindowDimensions()` so the size updates automatically on rotation and resize.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-falsy-and-render`,id:`rn-no-falsy-and-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-falsy-and-render`,title:`Numeric && renders bare zero`,severity:`error`,recommendation:"When the number is 0, this shows a bare `0` as text, which crashes on RN. Use `{value > 0 && <X />}`, `{Boolean(value) && <X />}`, or `{value ? <X /> : null}`.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-image-children`,id:`rn-no-image-children`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-image-children`,title:`Children inside react-native <Image>`,severity:`error`,recommendation:"React Native's `<Image>` can't render children. Use `<ImageBackground>` (same `source`/`style` props) to layer content over an image.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-inline-flatlist-renderitem`,id:`rn-no-inline-flatlist-renderitem`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-inline-flatlist-renderitem`,title:`Inline renderItem on list`,severity:`warn`,recommendation:`Move renderItem to a named function or wrap it in useCallback so it is not rebuilt every time the screen redraws.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-inline-object-in-list-item`,id:`rn-no-inline-object-in-list-item`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-inline-object-in-list-item`,title:`Inline object in list renderItem`,severity:`warn`,recommendation:`Move style and object props out of renderItem (StyleSheet.create, useMemo at list scope, or pass primitives) so memo() rows stop redrawing when their data has not changed.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-legacy-expo-packages`,id:`rn-no-legacy-expo-packages`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-legacy-expo-packages`,title:`Unmaintained legacy Expo package`,severity:`warn`,recommendation:`Switch to the maintained replacement package so users are not stuck with unfixed bugs in deprecated Expo packages.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-legacy-shadow-styles`,id:`rn-no-legacy-shadow-styles`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-legacy-shadow-styles`,title:`Legacy platform-specific shadow styles`,severity:`warn`,recommendation:"Use `boxShadow` for shadows that work on both platforms on the new architecture, instead of platform-specific shadow properties.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-non-native-navigator`,id:`rn-no-non-native-navigator`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-non-native-navigator`,title:`Non-native JS navigator`,severity:`warn`,recommendation:"Use `@react-navigation/native-stack` for stack navigation. Treat drawers separately because standalone React Navigation has no native drawer replacement.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-panresponder`,id:`rn-no-panresponder`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-panresponder`,title:`PanResponder over react-native-gesture-handler`,severity:`warn`,recommendation:"Use `react-native-gesture-handler` (`Gesture.Pan()`) instead of `PanResponder`. It runs gestures on the native UI thread, so they stay smooth even when the JS thread is busy.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-raw-text`,id:`rn-no-raw-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-raw-text`,title:`Raw text outside a Text component`,severity:`error`,recommendation:"Text outside a `<Text>` component crashes on React Native. Wrap it like `<Text>{value}</Text>`.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-renderitem-key`,id:`rn-no-renderitem-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-renderitem-key`,title:`renderItem key is ignored by React Native lists`,severity:`warn`,recommendation:"A `key` on the JSX from renderItem does nothing, since lists get row keys from `keyExtractor` (or `item.key`). Remove it and set `keyExtractor` on the list instead.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-scroll-state`,id:`rn-no-scroll-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-scroll-state`,title:`setState in onScroll handler`,severity:`error`,recommendation:"`setState` on every scroll event redraws the screen dozens of times a second. Track the position with a Reanimated shared value (`useAnimatedScrollHandler`) or a ref.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-scrollview-mapped-list`,id:`rn-no-scrollview-mapped-list`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-scrollview-mapped-list`,title:`Non-virtualized mapped list in ScrollView`,severity:`warn`,recommendation:"`<ScrollView>{items.map(...)}</ScrollView>` builds every row at once, which slows scrolling. Use FlashList, LegendList, or FlatList instead.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-set-native-props`,id:`rn-no-set-native-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-set-native-props`,title:`Imperative setNativeProps (no-op under Fabric)`,severity:`warn`,recommendation:"Drive the prop through React state, an `Animated.Value` (with `useNativeDriver: true`), or a Reanimated shared value. `setNativeProps` is a silent no-op under the New Architecture.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-single-element-style-array`,id:`rn-no-single-element-style-array`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-single-element-style-array`,title:`Single-element style array adds wasted allocation`,severity:`warn`,recommendation:"Use `style={value}` instead of `style={[value]}`. A one-item array just adds extra work for nothing.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-platform-shaking-use-direct-import`,id:`rn-platform-shaking-use-direct-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-platform-shaking-use-direct-import`,title:`Platform reached through React Native namespace`,severity:`warn`,recommendation:'Import `Platform` directly with `import { Platform } from "react-native"` so Expo can remove code for the other platform.',category:`Bugs`,framework:`react-native`,requires:[`expo:54`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-content-inset-adjustment`,id:`rn-prefer-content-inset-adjustment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-content-inset-adjustment`,title:`Manual safe-area insets can duplicate offsets`,severity:`warn`,recommendation:`Retired: SafeAreaView wrappers are valid; prefer native content inset adjustment only when manual inset plumbing causes scroll jumps or duplicated safe-area offsets.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/rn-prefer-expo-image`,id:`rn-prefer-expo-image`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-expo-image`,title:`react-native Image instead of expo-image`,severity:`warn`,recommendation:"Use `<Image>` from `expo-image` instead of `react-native`. Same props, plus caching, placeholders, and crossfades for faster image loading.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-pressable`,id:`rn-prefer-pressable`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-pressable`,title:`Touchable components instead of Pressable`,severity:`warn`,recommendation:"Use `<Pressable>` because Touchable* components are frozen and lack Pressable's state-based feedback and accessibility behavior.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-pressable-over-gesture-detector`,id:`rn-prefer-pressable-over-gesture-detector`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-pressable-over-gesture-detector`,title:`GestureDetector for a plain tap`,severity:`warn`,recommendation:"Every GestureDetector sets up a native handler when it appears, which slows the screen. For a plain tap, use `<Pressable>` (or `createCSSAnimatedComponent(Pressable)` from react-native-reanimated/css for animated press feedback).",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-reanimated`,id:`rn-prefer-reanimated`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-reanimated`,title:`JS-thread animation instead of Reanimated`,severity:`warn`,recommendation:"Use `import Animated from 'react-native-reanimated'` so animations run on the UI thread instead of the JS thread, which keeps them smooth.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-pressable-shared-value-mutation`,id:`rn-pressable-shared-value-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-pressable-shared-value-mutation`,title:`Pressable animates on the JS thread`,severity:`warn`,recommendation:`This press animation runs on the JS thread, so it can stutter. Wrap it in <GestureDetector gesture={Gesture.Tap()...}> to run it on the UI thread.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-reanimated-4-no-legacy-spring-thresholds`,id:`rn-reanimated-4-no-legacy-spring-thresholds`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-reanimated-4-no-legacy-spring-thresholds`,title:`Legacy Reanimated spring threshold`,severity:`warn`,recommendation:"Replace Reanimated 3 rest thresholds with Reanimated 4's `energyThreshold` spring option.",category:`Bugs`,framework:`react-native`,requires:[`reanimated:4`],tags:[`react-native`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/rn-reanimated-4-no-removed-api`,id:`rn-reanimated-4-no-removed-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-reanimated-4-no-removed-api`,title:`API removed in Reanimated 4`,severity:`warn`,recommendation:`Migrate removed Reanimated APIs to their Reanimated 4 or Gesture API replacements before upgrading.`,category:`Bugs`,framework:`react-native`,requires:[`reanimated:4`],tags:[`react-native`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/rn-reanimated-4-use-worklets-scheduler`,id:`rn-reanimated-4-use-worklets-scheduler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-reanimated-4-use-worklets-scheduler`,title:`Scheduler moved to Worklets`,severity:`warn`,recommendation:"Import the corresponding scheduler from `react-native-worklets` when migrating to Reanimated 4.",category:`Bugs`,framework:`react-native`,requires:[`reanimated:4`],tags:[`react-native`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/rn-scrollview-dynamic-padding`,id:`rn-scrollview-dynamic-padding`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-scrollview-dynamic-padding`,title:`Dynamic padding on contentContainerStyle`,severity:`warn`,recommendation:"Use `contentInset={{ bottom: dynamicValue }}` so the OS shifts the content instead of relaying it out, which avoids the jump.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-scrollview-flex-in-content-container`,id:`rn-scrollview-flex-in-content-container`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-scrollview-flex-in-content-container`,title:`flex on contentContainerStyle collapses container`,severity:`warn`,recommendation:"In RN, `flex: 1` on `contentContainerStyle` sets `flexBasis: 0` and can collapse the container on small screens. Use `flexGrow: 1` instead.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-style-prefer-boxshadow`,id:`rn-style-prefer-boxshadow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-style-prefer-boxshadow`,title:`Platform-specific shadow keys over boxShadow`,severity:`warn`,recommendation:'These shadow keys only work on one platform. On the New Architecture, use a CSS `boxShadow` string like `boxShadow: "0 2px 8px rgba(0,0,0,0.1)"`, which works on both.',category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/role-button-requires-complete-keyboard-activation`,id:`role-button-requires-complete-keyboard-activation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`role-button-requires-complete-keyboard-activation`,title:`ARIA button handles only one activation key`,severity:`warn`,recommendation:`Use a native button, or make a custom role button activate with both Enter and Space.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/role-has-required-aria-props`,id:`role-has-required-aria-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`role-has-required-aria-props`,title:`Role missing required ARIA props`,severity:`error`,recommendation:"Add every required `aria-*` attribute so assistive tech can expose the role's state correctly.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/role-supports-aria-props`,id:`role-supports-aria-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`role-supports-aria-props`,title:`Unsupported ARIA prop for role`,severity:`warn`,recommendation:"Only use `aria-*` attributes that the element's role supports.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/rules-of-hooks`,id:`rules-of-hooks`,source:`react-doctor`,originallyExternal:!0,rule:{id:`rules-of-hooks`,title:`Hook called conditionally`,severity:`error`,recommendation:`Call hooks at the top level of a React function component or custom Hook so React sees the same hook order on every render.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/scope`,id:`scope`,source:`react-doctor`,originallyExternal:!0,rule:{id:`scope`,title:`scope attribute on non-th element`,severity:`warn`,recommendation:"Remove `scope` from this element or move it to the related `<th>` cell.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/secret-in-fallback`,id:`secret-in-fallback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`secret-in-fallback`,title:`Hardcoded secret fallback for env var`,severity:`error`,recommendation:"Remove the literal fallback and fail closed (throw when the variable is unset). The hardcoded value is a committed secret, and the `??`/`||` default makes the app run with it in any environment that forgot to set the var.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/self-closing-comp`,id:`self-closing-comp`,source:`react-doctor`,originallyExternal:!0,rule:{id:`self-closing-comp`,title:`Element not self-closing`,severity:`warn`,recommendation:"Use `<X />` for childless elements so empty closing tags do not add noise.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/server-after-nonblocking`,id:`server-after-nonblocking`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-after-nonblocking`,title:`Blocking side effect before response`,severity:`warn`,recommendation:"`import { after } from 'next/server'`, then wrap it: `after(() => analytics.track(...))`. The response sends right away.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-auth-actions`,id:`server-auth-actions`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-auth-actions`,title:`Unauthenticated server action can be called directly`,severity:`error`,recommendation:`Check auth before changing server state or invoking billable services because exported server actions can be called directly by unauthenticated clients.`,category:`Bugs`,framework:`global`,tags:[`server-action`],isScanRule:!1}},{key:`react-doctor/server-cache-with-object-literal`,id:`server-cache-with-object-literal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-cache-with-object-literal`,title:`React.cache with object literal`,severity:`warn`,recommendation:"Pass plain values like strings or numbers, not an object. React.cache() matches the exact value, so a new `{}` each render misses the cache.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-dedup-props`,id:`server-dedup-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-dedup-props`,title:`Duplicate data in server props`,severity:`warn`,recommendation:`Pass the source array once and build the other version on the client. Sending both ships the data twice to the browser.`,category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-fetch-without-revalidate`,id:`server-fetch-without-revalidate`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-fetch-without-revalidate`,title:`Fetch without revalidate`,severity:`warn`,recommendation:'Pass `{ next: { revalidate: <seconds> } }` (or `cache: "no-store"`) so old data doesn\'t stick around.',category:`Bugs`,framework:`global`,disabledWhen:[`nextjs:15`,`nextjs:static-export`],tags:[`server-action`],isScanRule:!1}},{key:`react-doctor/server-hoist-static-io`,id:`server-hoist-static-io`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-hoist-static-io`,title:`Static file read on every request`,severity:`warn`,recommendation:"Move the read to module scope so it runs once at load: `const FONT_DATA = await fetch(new URL('./fonts/Inter.ttf', import.meta.url)).then(r => r.arrayBuffer())`.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-no-mutable-module-state`,id:`server-no-mutable-module-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-no-mutable-module-state`,title:`Mutable module state on the server`,severity:`error`,recommendation:"Keep per-request data inside the action, or in headers, cookies, or `React.cache`. Module-scope `let`/`var` is shared by every request.",category:`Bugs`,framework:`global`,tags:[`server-action`],isScanRule:!1}},{key:`react-doctor/server-sequential-independent-await`,id:`server-sequential-independent-await`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-sequential-independent-await`,title:`Sequential independent awaits`,severity:`warn`,recommendation:"These two awaits don't depend on each other. Wrap them in `Promise.all([...])` so they run at the same time.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/shadcn-tabs-trigger-requires-list`,id:`shadcn-tabs-trigger-requires-list`,source:`react-doctor`,originallyExternal:!1,rule:{id:`shadcn-tabs-trigger-requires-list`,title:`Tabs trigger is outside TabsList`,severity:`warn`,recommendation:`Render each imported TabsTrigger inside its library TabsList so keyboard navigation and tablist semantics share one scope.`,category:`Bugs`,framework:`global`,defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/state-in-constructor`,id:`state-in-constructor`,source:`react-doctor`,originallyExternal:!0,rule:{id:`state-in-constructor`,title:`State initialized in constructor`,severity:`warn`,recommendation:`Use one class-state setup pattern so readers know where initial state lives.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/style-prop-object`,id:`style-prop-object`,source:`react-doctor`,originallyExternal:!0,rule:{id:`style-prop-object`,title:`Style prop is not an object`,severity:`warn`,recommendation:"Pass `style` as an object so React can apply CSS properties instead of ignoring a string style value.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/styled-components-duplicate-css-property-in-block`,id:`styled-components-duplicate-css-property-in-block`,source:`react-doctor`,originallyExternal:!1,rule:{id:`styled-components-duplicate-css-property-in-block`,title:`Duplicate CSS property in styled block`,severity:`warn`,recommendation:`Merge repeated declarations of the same CSS property in a styled block into one, so a later conditional value doesn't silently override an earlier one.`,category:`Maintainability`,framework:`global`,requires:[`styled-components`],tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/styled-components-non-transient-custom-prop-on-intrinsic-element`,id:`styled-components-non-transient-custom-prop-on-intrinsic-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`styled-components-non-transient-custom-prop-on-intrinsic-element`,title:`Non-transient custom prop on styled intrinsic element`,severity:`warn`,recommendation:"Prefix custom styled-components props with `$` (e.g. `$active`) so styled-components v6 keeps them off the DOM node instead of forwarding them as invalid attributes.",category:`Bugs`,framework:`global`,requires:[`styled-components:6`],isScanRule:!1}},{key:`react-doctor/supabase-client-owned-authz-field`,id:`supabase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`supabase-client-owned-authz-field`,title:`Client writes Supabase authorization field`,severity:`error`,recommendation:"Use RLS policies based on `auth.uid()` and server-owned membership rows; do not trust client-provided owner, org, or role columns.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/supabase-rls-policy-risk`,id:`supabase-rls-policy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`supabase-rls-policy-risk`,title:`Permissive Supabase RLS policy`,severity:`error`,recommendation:"Keep public-read policies explicit, but gate inserts, updates, deletes, and service-role bypasses behind `auth.uid()` plus trusted tenant membership.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/supabase-table-missing-rls`,id:`supabase-table-missing-rls`,source:`react-doctor`,originallyExternal:!1,rule:{id:`supabase-table-missing-rls`,title:`Supabase table created without Row Level Security`,severity:`error`,recommendation:"Enable RLS in the same migration (`alter table <name> enable row level security;`) and add `auth.uid()`-scoped policies for select/insert/update/delete. A public table without RLS is fully readable and writable with the public anon key.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/svg-filter-clickjacking-risk`,id:`svg-filter-clickjacking-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`svg-filter-clickjacking-risk`,title:`SVG-filtered iframe clickjacking primitive`,severity:`warn`,recommendation:"Avoid filtering cross-origin iframes. Use `frame-ancestors` on sensitive pages and keep SVG filters away from embedded privileged UI.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/tabindex-no-positive`,id:`tabindex-no-positive`,source:`react-doctor`,originallyExternal:!0,rule:{id:`tabindex-no-positive`,title:`Positive tabindex value`,severity:`warn`,recommendation:"Use `tabIndex={0}` (focusable in source order) or `tabIndex={-1}` (focus only in code).",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/tanstack-start-get-mutation`,id:`tanstack-start-get-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-get-mutation`,title:`Mutation in GET server function`,severity:`warn`,recommendation:"Use `createServerFn({ method: 'POST' })` for data changes. GET requests can be prefetched and are open to CSRF.",category:`Security`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-loader-parallel-fetch`,id:`tanstack-start-loader-parallel-fetch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-loader-parallel-fetch`,title:`Sequential awaits in loader`,severity:`warn`,recommendation:"Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to avoid request waterfalls in route loaders",category:`Performance`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-missing-head-content`,id:`tanstack-start-missing-head-content`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-missing-head-content`,title:`Root route missing HeadContent`,severity:`warn`,recommendation:"Add `<HeadContent />` inside `<head>` in your __root route. Without it, route `head()` meta tags are dropped.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-missing-scripts`,id:`tanstack-start-missing-scripts`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-missing-scripts`,title:`Root route missing Scripts`,severity:`warn`,recommendation:"Render `<Scripts />` near the end of `<body>` in your __root route so TanStack Start can load client-side JavaScript.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-anchor-element`,id:`tanstack-start-no-anchor-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-anchor-element`,title:`Plain anchor reloads TanStack Router navigation`,severity:`warn`,recommendation:"Use `Link` from `@tanstack/react-router` so internal navigation keeps client state, preloading, and typed routes.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-direct-fetch-in-loader`,id:`tanstack-start-no-direct-fetch-in-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-direct-fetch-in-loader`,title:`Direct fetch in route loader`,severity:`warn`,recommendation:"Use `createServerFn()` from @tanstack/react-start for type-safe RPC, input validation, and proper server/client code splitting",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-dynamic-server-fn-import`,id:`tanstack-start-no-dynamic-server-fn-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-dynamic-server-fn-import`,title:`Dynamic server function import`,severity:`error`,recommendation:"Use `import { myFn } from '~/utils/my.functions'`. The bundler only swaps server code for RPC stubs on static imports.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-navigate-in-render`,id:`tanstack-start-no-navigate-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-navigate-in-render`,title:`navigate() called during render`,severity:`warn`,recommendation:"Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader`. navigate() during render causes hydration issues.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-secrets-in-loader`,id:`tanstack-start-no-secrets-in-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-secrets-in-loader`,title:`Secret exposed in a loader`,severity:`error`,recommendation:"Loaders are isomorphic (run on both server and client). Wrap secret access in `createServerFn()` so it stays server-only",category:`Security`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-use-server-in-handler`,id:`tanstack-start-no-use-server-in-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-use-server-in-handler`,title:`Redundant use server in handler`,severity:`error`,recommendation:`TanStack Start handles server boundaries automatically via the Vite plugin. "use server" inside createServerFn causes compile errors.`,category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-useeffect-fetch`,id:`tanstack-start-no-useeffect-fetch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-useeffect-fetch`,title:`fetch inside useEffect in route`,severity:`warn`,recommendation:"Fetch data in the route `loader` instead. The router loads it before rendering and avoids waterfalls.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-redirect-in-try-catch`,id:`tanstack-start-redirect-in-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-redirect-in-try-catch`,title:`redirect() inside try-catch`,severity:`warn`,recommendation:"TanStack Router's `redirect()` and `notFound()` throw special errors caught by the router. Move them outside the try block or re-throw in the catch",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-route-property-order`,id:`tanstack-start-route-property-order`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-route-property-order`,title:`Route property order breaks type inference`,severity:`error`,recommendation:`Follow the route property order because TanStack Router's type inference depends on earlier properties feeding later ones.`,category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-server-fn-method-order`,id:`tanstack-start-server-fn-method-order`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-server-fn-method-order`,title:`Server function method order breaks type inference`,severity:`error`,recommendation:`Chain methods in order: .middleware() → .validator() → .client() → .server() → .handler(). Types depend on this sequence.`,category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-server-fn-validate-input`,id:`tanstack-start-server-fn-validate-input`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-server-fn-validate-input`,title:`Server function without input validation`,severity:`warn`,recommendation:"Add `.validator(schema)` before `.handler()`. This data crosses the network and must be validated at runtime.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tenant-static-proxy-risk`,id:`tenant-static-proxy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tenant-static-proxy-risk`,title:`Tenant-controlled static asset proxy`,severity:`warn`,recommendation:`Bind tenant identity to the trusted host or authenticated org, canonicalize after decoding, reject traversal, and never let one tenant choose another tenant's asset prefix.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/three-cap-device-pixel-ratio`,id:`three-cap-device-pixel-ratio`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-cap-device-pixel-ratio`,title:`Unbounded Three.js device pixel ratio`,severity:`warn`,recommendation:`Cap renderer pixel ratio, commonly at 2, so high-density displays do not multiply GPU work without a bound`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-limit-shadowed-point-lights`,id:`three-limit-shadowed-point-lights`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-limit-shadowed-point-lights`,title:`Too many shadow-casting Three.js point lights`,severity:`warn`,recommendation:`Keep at most two shadow-casting point lights in one scene, or use cheaper directional, spot, baked, or fake shadows`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-allocation-in-pointer-move`,id:`three-no-allocation-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-allocation-in-pointer-move`,title:`Three.js allocation inside pointer-move handler`,severity:`warn`,recommendation:`Reuse vectors, raycasters, and other Three.js objects while handling continuous pointer movement`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-async-animation-loop`,id:`three-no-async-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-async-animation-loop`,title:`Async Three.js animation callback`,severity:`warn`,recommendation:`Keep animation callbacks synchronous; start asynchronous work outside the loop and consume completed state during frames`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-clone-in-animation-loop`,id:`three-no-clone-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-clone-in-animation-loop`,title:`Three.js clone inside animation loop`,severity:`warn`,recommendation:`Clone once before the animation loop or copy values into a reusable scratch object during frames`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-material-recompile-in-animation-loop`,id:`three-no-material-recompile-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-material-recompile-in-animation-loop`,title:`Material recompiled inside animation loop`,severity:`error`,recommendation:`Change material defines or features outside the animation loop and set needsUpdate only when the compiled program must change`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-new-in-animation-loop`,id:`three-no-new-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-new-in-animation-loop`,title:`Allocation inside Three.js animation loop`,severity:`warn`,recommendation:`Allocate reusable objects before the animation loop and mutate them in place during each frame`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-object-construction-in-render`,id:`three-no-object-construction-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-object-construction-in-render`,title:`Three.js object constructed during React render`,severity:`warn`,recommendation:`Construct mutable Three.js objects in a stable initializer, effect, event, or module scope instead of recreating them during React render`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-redundant-uniforms-need-update`,id:`three-no-redundant-uniforms-need-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-redundant-uniforms-need-update`,title:`Redundant uniformsNeedUpdate inside animation loop`,severity:`warn`,recommendation:`Mutate ShaderMaterial uniform values directly; Three.js uploads custom uniforms during rendering`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-state-in-animation-loop`,id:`three-no-state-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-state-in-animation-loop`,title:`React state update inside Three.js animation loop`,severity:`warn`,recommendation:`Mutate Three.js objects or refs during frames; reserve React state for guarded, infrequent transitions`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-state-in-pointer-move`,id:`three-no-state-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-state-in-pointer-move`,title:`React state update inside Three.js pointer-move handler`,severity:`warn`,recommendation:`Keep continuous pointer previews in Three.js objects or refs and publish React state when the interaction commits`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-on-before-compile-require-program-cache-key`,id:`three-on-before-compile-require-program-cache-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-on-before-compile-require-program-cache-key`,title:`onBeforeCompile variant lacks a program cache key`,severity:`error`,recommendation:`Return every mutable shader-patch variant from customProgramCacheKey on the same material`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-prefer-instanced-mesh`,id:`three-prefer-instanced-mesh`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-prefer-instanced-mesh`,title:`Repeated Three.js meshes use separate draw calls`,severity:`warn`,recommendation:`Replace repeated Mesh objects that share geometry and material with one InstancedMesh and per-instance transforms`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-raw-shader-require-fragment-float-precision`,id:`three-raw-shader-require-fragment-float-precision`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-raw-shader-require-fragment-float-precision`,title:`Raw fragment shader lacks float precision`,severity:`error`,recommendation:`Declare a default fragment float precision or qualify every floating-point declaration`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-raw-shader-require-glsl3-version`,id:`three-raw-shader-require-glsl3-version`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-raw-shader-require-glsl3-version`,title:`Raw shader uses GLSL 3 syntax without GLSL3`,severity:`error`,recommendation:`Set glslVersion to THREE.GLSL3 when RawShaderMaterial uses GLSL 3 syntax`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-animation-mixer-cleanup`,id:`three-require-animation-mixer-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-animation-mixer-cleanup`,title:`Unreleased Three.js animation mixer actions`,severity:`warn`,recommendation:`Stop component-owned AnimationMixer actions and uncache their owned root in React cleanup`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-controls-cleanup`,id:`three-require-controls-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-controls-cleanup`,title:`Undisposed imperative Three.js controls`,severity:`warn`,recommendation:`Dispose component-owned Three.js controls in a React cleanup so their DOM listeners are removed`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-frame-delta`,id:`three-require-frame-delta`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-frame-delta`,title:`Frame-rate-dependent Three.js animation`,severity:`warn`,recommendation:`Scale incremental transforms and interpolation by Clock.getDelta(), use delta-aware damping, or assign from absolute animation time`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-instanced-buffer-update`,id:`three-require-instanced-buffer-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-instanced-buffer-update`,title:`Three.js instanced mesh buffer is not marked for upload`,severity:`error`,recommendation:`After setMatrixAt or setColorAt, set the matching instance buffer's needsUpdate flag to true`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-owned-geometry-cleanup`,id:`three-require-owned-geometry-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-owned-geometry-cleanup`,title:`Locally owned Three.js geometry is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed geometries when their React owner releases them`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-owned-material-cleanup`,id:`three-require-owned-material-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-owned-material-cleanup`,title:`Locally owned Three.js material is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed materials when their React owner releases them`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-owned-texture-cleanup`,id:`three-require-owned-texture-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-owned-texture-cleanup`,title:`Locally owned Three.js texture is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed textures when their React owner releases them`,category:`Performance`,framework:`global`,requires:[`react`,`three`],disabledWhen:[`r3f`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-postprocessing-cleanup`,id:`three-require-postprocessing-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-postprocessing-cleanup`,title:`Undisposed Three.js postprocessing resource`,severity:`warn`,recommendation:`Dispose component-owned postprocessing composers and resource-owning passes`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-projection-matrix-update`,id:`three-require-projection-matrix-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-projection-matrix-update`,title:`Missing Three.js camera projection-matrix update`,severity:`error`,recommendation:`Call camera.updateProjectionMatrix() after changing projection properties so Three.js renders the new frustum`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-render-target-cleanup`,id:`three-require-render-target-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-render-target-cleanup`,title:`Undisposed Three.js render target`,severity:`warn`,recommendation:`Dispose component-owned render targets in a React cleanup that follows the render target binding`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-renderer-cleanup`,id:`three-require-renderer-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-renderer-cleanup`,title:`Undisposed Three.js renderer`,severity:`warn`,recommendation:`Dispose component-owned renderers and stop their animation loop or animation frame in matching React cleanup`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-constant-out-of-bounds-index`,id:`three-shader-no-constant-out-of-bounds-index`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-constant-out-of-bounds-index`,title:`Shader uses a constant out-of-bounds index`,severity:`error`,recommendation:`Keep constant array, vector, and matrix indices within their declared bounds`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-derivatives-in-nonuniform-flow`,id:`three-shader-no-derivatives-in-nonuniform-flow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-derivatives-in-nonuniform-flow`,title:`Shader derivative runs in non-uniform control flow`,severity:`error`,recommendation:`Evaluate derivatives and implicit-LOD texture samples before fragment-input-dependent branches`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-clamp-bounds`,id:`three-shader-no-invalid-clamp-bounds`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-clamp-bounds`,title:`Shader uses invalid clamp bounds`,severity:`error`,recommendation:`Keep clamp minVal less than or equal to maxVal`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-constant-bit-operations`,id:`three-shader-no-invalid-constant-bit-operations`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-constant-bit-operations`,title:`Shader uses invalid constant bit operations`,severity:`error`,recommendation:`Keep shift counts and bitfield ranges within the integer bit width`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-constant-math`,id:`three-shader-no-invalid-constant-math`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-constant-math`,title:`Shader uses invalid constant math`,severity:`error`,recommendation:`Keep constant arguments within the defined GLSL builtin and operator domains`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-smoothstep-edges`,id:`three-shader-no-invalid-smoothstep-edges`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-smoothstep-edges`,title:`Shader uses invalid smoothstep edges`,severity:`error`,recommendation:`Keep smoothstep edge0 below edge1 and invert the result when a descending transition is needed`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-inverse-of-uniform`,id:`three-shader-no-inverse-of-uniform`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-inverse-of-uniform`,title:`Shader inverts a uniform matrix per invocation`,severity:`warn`,recommendation:`Compute uniform matrix inverses once on the CPU and upload the result`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-redeclared-builtins`,id:`three-shader-no-redeclared-builtins`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-redeclared-builtins`,title:`ShaderMaterial redeclares a Three.js builtin`,severity:`error`,recommendation:`Use ShaderMaterial built-in attributes and uniforms without redeclaring them`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-redundant-frag-depth`,id:`three-shader-no-redundant-frag-depth`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-redundant-frag-depth`,title:`Shader redundantly writes fragment depth`,severity:`warn`,recommendation:`Let fixed-function depth supply gl_FragCoord.z when depth is unchanged`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-version-directive`,id:`three-shader-no-version-directive`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-version-directive`,title:`Three shader source contains a version directive`,severity:`error`,recommendation:`Set glslVersion on the material instead of writing #version in shader source`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-prefer-small-integer-pow`,id:`three-shader-prefer-small-integer-pow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-prefer-small-integer-pow`,title:`Shader uses pow for a small integer exponent`,severity:`warn`,recommendation:`Use explicit multiplication for powers of two, three, or four`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-prefer-squared-distance-comparison`,id:`three-shader-prefer-squared-distance-comparison`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-prefer-squared-distance-comparison`,title:`Shader compares a computed distance`,severity:`warn`,recommendation:`Compare squared vector distance against a squared nonnegative threshold`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-matching-uniforms`,id:`three-shader-require-matching-uniforms`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-matching-uniforms`,title:`Shader stages declare incompatible uniforms`,severity:`error`,recommendation:`Use the same type, array dimensions, and precision for uniforms shared by both shader stages`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-matching-varyings`,id:`three-shader-require-matching-varyings`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-matching-varyings`,title:`Shader stage interface does not match`,severity:`error`,recommendation:`Declare every statically used fragment input as a compatible vertex output with the same name`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-position-on-all-paths`,id:`three-shader-require-position-on-all-paths`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-position-on-all-paths`,title:`Vertex shader leaves gl_Position undefined`,severity:`error`,recommendation:`Assign gl_Position before every return and on every path through main`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-uniform-bindings`,id:`three-shader-require-uniform-bindings`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-uniform-bindings`,title:`Shader uniform has no material binding`,severity:`error`,recommendation:`Add every custom GLSL uniform to the ShaderMaterial uniforms object with a compatible value`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-tsl-no-js-uniform-branch`,id:`three-tsl-no-js-uniform-branch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-tsl-no-js-uniform-branch`,title:`JavaScript branch reads a TSL uniform value`,severity:`warn`,recommendation:`Express uniform-dependent shader control flow with TSL If, select, or Loop nodes so it runs on the GPU`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-webgpu-no-legacy-effect-composer`,id:`three-webgpu-no-legacy-effect-composer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-webgpu-no-legacy-effect-composer`,title:`Legacy EffectComposer used with WebGPURenderer`,severity:`error`,recommendation:`Use WebGPURenderer's node-based post-processing pipeline instead of the legacy WebGL EffectComposer`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-webgpu-no-legacy-material-api`,id:`three-webgpu-no-legacy-material-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-webgpu-no-legacy-material-api`,title:`Legacy material API used with WebGPURenderer`,severity:`error`,recommendation:`Use Three.js node materials and TSL for custom shaders rendered by WebGPURenderer`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/unsafe-json-in-html`,id:`unsafe-json-in-html`,source:`react-doctor`,originallyExternal:!1,rule:{id:`unsafe-json-in-html`,title:`Unescaped JSON in HTML or script sink`,severity:`warn`,recommendation:'JSON.stringify does not HTML-escape, so a `<\/script>` (or `<`) in the data breaks out and becomes XSS. Use an HTML-safe serializer (serialize-javascript, devalue) or escape `<`, `>`, and `&`, or pass data via a JSON `<script type="application/json">` read with JSON.parse.',category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/untrusted-redirect-following`,id:`untrusted-redirect-following`,source:`react-doctor`,originallyExternal:!1,rule:{id:`untrusted-redirect-following`,title:`Server fetch follows redirects for caller-shaped URL`,severity:`warn`,recommendation:'Use `redirect: "manual"` or equivalent and re-validate every redirect target before following it to avoid SSRF redirect bypasses.',category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/url-prefilled-privileged-action`,id:`url-prefilled-privileged-action`,source:`react-doctor`,originallyExternal:!1,rule:{id:`url-prefilled-privileged-action`,title:`URL pre-fills a privileged action`,severity:`warn`,recommendation:`Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/use-lazy-motion`,id:`use-lazy-motion`,source:`react-doctor`,originallyExternal:!1,rule:{id:`use-lazy-motion`,title:`Full Framer Motion import`,severity:`warn`,recommendation:'Use `import { LazyMotion, m } from "framer-motion"` with `domAnimation` features. Saves about 30kb.',category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/valtio-no-proxy-read-in-render`,id:`valtio-no-proxy-read-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`valtio-no-proxy-read-in-render`,title:`Valtio proxy read during render`,severity:`warn`,recommendation:`Read reactive render values from useSnapshot. Reserve the mutable proxy for event handlers, effects, and other callbacks that need the latest value.`,category:`Bugs`,framework:`global`,requires:[`react`,`valtio:1`],isScanRule:!1}},{key:`react-doctor/valtio-no-snapshot-in-callback`,id:`valtio-no-snapshot-in-callback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`valtio-no-snapshot-in-callback`,title:`Valtio snapshot read in a callback`,severity:`warn`,recommendation:"Read from the original Valtio proxy inside callbacks. `useSnapshot()` results are for render reads; callback reads can become tracked render dependencies and cause extra re-renders.",category:`Bugs`,framework:`global`,requires:[`react`,`valtio`,`valtio:1`],isScanRule:!1}},{key:`react-doctor/void-dom-elements-no-children`,id:`void-dom-elements-no-children`,source:`react-doctor`,originallyExternal:!0,rule:{id:`void-dom-elements-no-children`,title:`Children on a void element`,severity:`warn`,recommendation:`Remove the children or use a non-void tag so React does not drop content the element cannot render.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/waapi-animation-in-render`,id:`waapi-animation-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`waapi-animation-in-render`,title:`Web Animation starts during render`,severity:`error`,recommendation:`Start element.animate() from an effect or interaction handler so React renders stay pure.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/web-animation-offsets-valid`,id:`web-animation-offsets-valid`,source:`react-doctor`,originallyExternal:!1,rule:{id:`web-animation-offsets-valid`,title:`Invalid Web Animation keyframe offsets`,severity:`error`,recommendation:`Keep explicit Web Animation keyframe offsets between 0 and 1 and in nondecreasing order.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/webgl-no-sync-readback-in-animation-loop`,id:`webgl-no-sync-readback-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`webgl-no-sync-readback-in-animation-loop`,title:`Synchronous GPU readback inside animation loop`,severity:`warn`,recommendation:`Move GPU readback to a discrete or asynchronous path and reuse the latest completed result during frames`,category:`Performance`,framework:`global`,tags:[`webgl`],isScanRule:!1}},{key:`react-doctor/webhook-signature-risk`,id:`webhook-signature-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`webhook-signature-risk`,title:`Webhook handler lacks signature verification`,severity:`warn`,recommendation:`Verify provider signatures before parsing or acting on webhook bodies. Use provider SDK helpers or HMAC verification with timing-safe comparison.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/window-open-without-noopener`,id:`window-open-without-noopener`,source:`react-doctor`,originallyExternal:!1,rule:{id:`window-open-without-noopener`,title:`window.open without noopener`,severity:`warn`,recommendation:"Pass `'noopener'` in the third features argument of `window.open` so the opened page can't control your tab through `window.opener`. Add `'noreferrer'` too when the destination must not receive the referrer.",category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/zod-v4-no-deprecated-error-apis`,id:`zod-v4-no-deprecated-error-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-no-deprecated-error-apis`,title:`Zod 3 error API breaks in Zod 4`,severity:`warn`,recommendation:"Use the Zod 4 helpers instead: `z.treeifyError()`, `z.flattenError()`, `z.prettifyError()`, or read `error.issues` directly.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zod-v4-no-deprecated-error-customization`,id:`zod-v4-no-deprecated-error-customization`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-no-deprecated-error-customization`,title:`Zod 3 error customization breaks in Zod 4`,severity:`warn`,recommendation:"In Zod 4, use the single `{ error }` option instead of message strings, `invalid_type_error`, `required_error`, or `errorMap`.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zod-v4-no-deprecated-schema-apis`,id:`zod-v4-no-deprecated-schema-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-no-deprecated-schema-apis`,title:`Zod 3 schema API breaks in Zod 4`,severity:`warn`,recommendation:"Switch to the Zod 4 versions: top-level factories like `z.enum()`, object helpers like `z.strictObject()`, the new `z.function({ input, output })` form, and explicit key/value schemas for `z.record()`.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zod-v4-prefer-top-level-string-formats`,id:`zod-v4-prefer-top-level-string-formats`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-prefer-top-level-string-formats`,title:`Format method on z.string()`,severity:`warn`,recommendation:"Use the Zod 4 top-level format checks like `z.email()`, `z.uuid()`, or `z.ipv4()` instead of `z.string().<format>()`.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zustand-no-fresh-selector-result`,id:`zustand-no-fresh-selector-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-fresh-selector-result`,title:`Zustand selector returns a fresh value`,severity:`error`,recommendation:"Select a stable store field, split the selector, or wrap a collection selector with `useShallow`.",category:`Performance`,framework:`global`,requires:[`react`,`zustand`,`zustand:5`],isScanRule:!1}},{key:`react-doctor/zustand-no-get-during-initialization`,id:`zustand-no-get-during-initialization`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-get-during-initialization`,title:`Zustand get() called during store initialization`,severity:`error`,recommendation:"Move the read into a deferred store action or derive the initial value without calling `get()`.",category:`Bugs`,framework:`global`,requires:[`react`,`zustand`,`zustand:1`],isScanRule:!1}},{key:`react-doctor/zustand-no-mutating-state`,id:`zustand-no-mutating-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-mutating-state`,title:`Zustand state mutated in place`,severity:`error`,recommendation:`Create a new object, array, Map, or Set before passing the updated value to Zustand.`,category:`Bugs`,framework:`global`,requires:[`react`,`zustand`,`zustand:1`],isScanRule:!1}},{key:`react-doctor/zustand-no-whole-store-destructure`,id:`zustand-no-whole-store-destructure`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-whole-store-destructure`,title:`Whole Zustand store subscribed during render`,severity:`warn`,recommendation:`Pass a selector to the Zustand hook so this component rerenders only when the state it reads changes.`,category:`Bugs`,framework:`global`,requires:[`react`,`zustand:1`],isScanRule:!1}}];const oe=e=>{if(e===`client-secret`)return ee;if(e===`static-export-redirect`)return y},$=ae.map(e=>{let{recommendationOverride:t,...n}=e.rule,r=oe(t);return{...e,rule:{...n,...r?{recommendationFor:r}:{}}}}),se=Object.fromEntries($.map(e=>[e.id,e.rule])),ce=[{key:`react-doctor/active-static-asset`,id:`active-static-asset`,source:`react-doctor`,originallyExternal:!1,rule:{...n,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...n.tags??[]])]}},{key:`react-doctor/agent-tool-capability-risk`,id:`agent-tool-capability-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...Q,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...Q.tags??[]])]}},{key:`react-doctor/artifact-baas-authority-surface`,id:`artifact-baas-authority-surface`,source:`react-doctor`,originallyExternal:!1,rule:{..._,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,..._.tags??[]])]}},{key:`react-doctor/artifact-env-leak`,id:`artifact-env-leak`,source:`react-doctor`,originallyExternal:!1,rule:{...z,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...z.tags??[]])]}},{key:`react-doctor/artifact-secret-leak`,id:`artifact-secret-leak`,source:`react-doctor`,originallyExternal:!1,rule:{...O,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...O.tags??[]])]}},{key:`react-doctor/build-pipeline-secret-boundary`,id:`build-pipeline-secret-boundary`,source:`react-doctor`,originallyExternal:!1,rule:{...P,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...P.tags??[]])]}},{key:`react-doctor/clickjacking-redirect-risk`,id:`clickjacking-redirect-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...H,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...H.tags??[]])]}},{key:`react-doctor/command-execution-input-risk`,id:`command-execution-input-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...R,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...R.tags??[]])]}},{key:`react-doctor/cors-cookie-trust-risk`,id:`cors-cookie-trust-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...G,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...G.tags??[]])]}},{key:`react-doctor/dangerous-html-sink`,id:`dangerous-html-sink`,source:`react-doctor`,originallyExternal:!1,rule:{...c,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...c.tags??[]])]}},{key:`react-doctor/firebase-client-owned-authz-field`,id:`firebase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{...w,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...w.tags??[]])]}},{key:`react-doctor/firebase-permissive-rules`,id:`firebase-permissive-rules`,source:`react-doctor`,originallyExternal:!1,rule:{...S,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...S.tags??[]])]}},{key:`react-doctor/firebase-query-filter-as-auth`,id:`firebase-query-filter-as-auth`,source:`react-doctor`,originallyExternal:!1,rule:{...l,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...l.tags??[]])]}},{key:`react-doctor/git-provider-url-injection-risk`,id:`git-provider-url-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...C,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...C.tags??[]])]}},{key:`react-doctor/import-metadata-execution-risk`,id:`import-metadata-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...t,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...t.tags??[]])]}},{key:`react-doctor/insecure-crypto-risk`,id:`insecure-crypto-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...Z,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...Z.tags??[]])]}},{key:`react-doctor/insecure-session-cookie`,id:`insecure-session-cookie`,source:`react-doctor`,originallyExternal:!1,rule:{...g,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...g.tags??[]])]}},{key:`react-doctor/jwt-insecure-verification`,id:`jwt-insecure-verification`,source:`react-doctor`,originallyExternal:!1,rule:{...d,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...d.tags??[]])]}},{key:`react-doctor/key-lifecycle-risk`,id:`key-lifecycle-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...u,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...u.tags??[]])]}},{key:`react-doctor/local-rpc-native-bridge-risk`,id:`local-rpc-native-bridge-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...s,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...s.tags??[]])]}},{key:`react-doctor/mcp-tool-capability-risk`,id:`mcp-tool-capability-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...h,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...h.tags??[]])]}},{key:`react-doctor/mdx-ssr-execution-risk`,id:`mdx-ssr-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...p,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...p.tags??[]])]}},{key:`react-doctor/nosql-injection-risk`,id:`nosql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...f,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...f.tags??[]])]}},{key:`react-doctor/package-metadata-secret`,id:`package-metadata-secret`,source:`react-doctor`,originallyExternal:!1,rule:{...F,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...F.tags??[]])]}},{key:`react-doctor/path-traversal-risk`,id:`path-traversal-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...e,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...e.tags??[]])]}},{key:`react-doctor/plugin-update-trust-risk`,id:`plugin-update-trust-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...I,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...I.tags??[]])]}},{key:`react-doctor/postmessage-origin-risk`,id:`postmessage-origin-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...m,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...m.tags??[]])]}},{key:`react-doctor/public-debug-artifact`,id:`public-debug-artifact`,source:`react-doctor`,originallyExternal:!1,rule:{...i,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...i.tags??[]])]}},{key:`react-doctor/public-env-secret-name`,id:`public-env-secret-name`,source:`react-doctor`,originallyExternal:!1,rule:{...a,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...a.tags??[]])]}},{key:`react-doctor/raw-sql-injection-risk`,id:`raw-sql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...b,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...b.tags??[]])]}},{key:`react-doctor/repository-secret-file`,id:`repository-secret-file`,source:`react-doctor`,originallyExternal:!1,rule:{...q,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...q.tags??[]])]}},{key:`react-doctor/request-body-mass-assignment`,id:`request-body-mass-assignment`,source:`react-doctor`,originallyExternal:!1,rule:{...r,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...r.tags??[]])]}},{key:`react-doctor/secret-in-fallback`,id:`secret-in-fallback`,source:`react-doctor`,originallyExternal:!1,rule:{...v,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...v.tags??[]])]}},{key:`react-doctor/supabase-client-owned-authz-field`,id:`supabase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{...k,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...k.tags??[]])]}},{key:`react-doctor/supabase-rls-policy-risk`,id:`supabase-rls-policy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...X,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...X.tags??[]])]}},{key:`react-doctor/supabase-table-missing-rls`,id:`supabase-table-missing-rls`,source:`react-doctor`,originallyExternal:!1,rule:{...K,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...K.tags??[]])]}},{key:`react-doctor/svg-filter-clickjacking-risk`,id:`svg-filter-clickjacking-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...D,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...D.tags??[]])]}},{key:`react-doctor/tenant-static-proxy-risk`,id:`tenant-static-proxy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...j,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...j.tags??[]])]}},{key:`react-doctor/unsafe-json-in-html`,id:`unsafe-json-in-html`,source:`react-doctor`,originallyExternal:!1,rule:{...M,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...M.tags??[]])]}},{key:`react-doctor/untrusted-redirect-following`,id:`untrusted-redirect-following`,source:`react-doctor`,originallyExternal:!1,rule:{...L,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...L.tags??[]])]}},{key:`react-doctor/url-prefilled-privileged-action`,id:`url-prefilled-privileged-action`,source:`react-doctor`,originallyExternal:!1,rule:{...B,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...B.tags??[]])]}},{key:`react-doctor/webhook-signature-risk`,id:`webhook-signature-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...A,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...A.tags??[]])]}}],le=$,ue=se,de=new Set($.map(e=>e.key)),fe=new Set($.filter(e=>e.rule.framework!==`global`).map(e=>e.key));export{de as ALL_REACT_DOCTOR_RULE_KEYS,T as CROSS_FILE_DEPENDENCY_COLLECTORS,W as CROSS_FILE_RULE_IDS,ne as EXTERNAL_RULES,fe as FRAMEWORK_SPECIFIC_RULE_KEYS,U as FRAMEWORK_TOKENS,ie as MOTION_LIBRARY_PACKAGES,Y as REACT_COMPILER_RULES,le as REACT_DOCTOR_RULES,ue as REACT_DOCTOR_RULE_REGISTRY,ce as REACT_DOCTOR_SCAN_RULES,J as REACT_NATIVE_DEPENDENCY_NAMES,x as REACT_NATIVE_DEPENDENCY_PREFIXES,te as UNBOUNDED_CROSS_FILE_RULE_IDS,re as classifySecurityScanFile,E as collectCrossFileDependencyProbes,o as isReactNativeDependencyName,N as resetManifestCaches,V as shouldReadSecurityScanContent};
13
+ If the missing value is recreated every render, move it inside the hook or stabilize it before adding it to deps.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/expo-no-non-inlined-env`,id:`expo-no-non-inlined-env`,source:`react-doctor`,originallyExternal:!1,rule:{id:`expo-no-non-inlined-env`,title:`Non-inlinable process.env access (Expo)`,severity:`warn`,recommendation:"Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",category:`Bugs`,framework:`react-native`,requires:[`expo`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/fieldset-requires-legend`,id:`fieldset-requires-legend`,source:`react-doctor`,originallyExternal:!1,rule:{id:`fieldset-requires-legend`,title:`Fieldset group has no legend`,severity:`warn`,recommendation:`Name grouped controls with a direct legend, or an explicit accessible name when no visible group label is appropriate.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/firebase-client-owned-authz-field`,id:`firebase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`firebase-client-owned-authz-field`,title:`Client writes authorization field`,severity:`error`,recommendation:`Derive authority fields on the server or enforce them in Firebase/Supabase rules; never trust client-provided owner, org, or role values.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/firebase-permissive-rules`,id:`firebase-permissive-rules`,source:`react-doctor`,originallyExternal:!1,rule:{id:`firebase-permissive-rules`,title:`Permissive Firebase security rule`,severity:`error`,recommendation:"Bind every read/write to `request.auth.uid`, immutable ownership, and tenant membership instead of treating sign-in as authorization.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/firebase-query-filter-as-auth`,id:`firebase-query-filter-as-auth`,source:`react-doctor`,originallyExternal:!1,rule:{id:`firebase-query-filter-as-auth`,title:`Firestore query filter used as authorization`,severity:`warn`,recommendation:"Make sure Firestore rules compare the requested document against `request.auth.uid` and trusted membership data.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/forbid-component-props`,id:`forbid-component-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forbid-component-props`,title:`Blocked component prop bypasses API contract`,severity:`warn`,recommendation:`Configure blocked component props so callers cannot bypass the component API contract.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/forbid-dom-props`,id:`forbid-dom-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forbid-dom-props`,title:`Blocked DOM prop bypasses project contract`,severity:`warn`,recommendation:`Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/forbid-elements`,id:`forbid-elements`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forbid-elements`,title:`Blocked element bypasses approved UI primitives`,severity:`warn`,recommendation:`Configure blocked elements so code stays on the approved UI primitives.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/form-control-requires-name`,id:`form-control-requires-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`form-control-requires-name`,title:`Form control is omitted from named submission data`,severity:`warn`,recommendation:`Give each data-bearing native control inside a form a stable name for FormData, autofill, and non-JavaScript submission.`,category:`Bugs`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/forward-ref-uses-ref`,id:`forward-ref-uses-ref`,source:`react-doctor`,originallyExternal:!0,rule:{id:`forward-ref-uses-ref`,title:`forwardRef without ref parameter`,severity:`warn`,recommendation:"Accept the `ref` parameter or drop `forwardRef` so parents are not promised a ref that never reaches the node.",category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/git-provider-url-injection-risk`,id:`git-provider-url-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`git-provider-url-injection-risk`,title:`Git provider URL built from interpolation`,severity:`warn`,recommendation:`Validate owner, repo, org, and branch identifiers against strict slugs and build URLs with URL/path encoders instead of raw interpolation.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/heading-has-content`,id:`heading-has-content`,source:`react-doctor`,originallyExternal:!0,rule:{id:`heading-has-content`,title:`Heading has no content`,severity:`warn`,recommendation:`Put readable text in every heading.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/hook-import-rename-loses-use-prefix`,id:`hook-import-rename-loses-use-prefix`,source:`react-doctor`,originallyExternal:!1,rule:{id:`hook-import-rename-loses-use-prefix`,title:`Hook import alias disables hook lint checks`,severity:`warn`,recommendation:"Keep a recognised `use*` name in custom-hook aliases, reserve bare `use` for React's `use` hook, and do not rename React hooks with name-specific lint semantics such as `useEffect`, `useImperativeHandle`, or `useEffectEvent`.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/hook-use-state`,id:`hook-use-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`hook-use-state`,title:`useState not destructured`,severity:`warn`,recommendation:"Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/hooks-no-nan-in-deps`,id:`hooks-no-nan-in-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`hooks-no-nan-in-deps`,title:`NaN in a hook dependency array`,severity:`warn`,recommendation:"Remove `NaN` (or `Number.NaN`) from the dependency array. If a value can be NaN at runtime, normalise it (`Number.isNaN(x) ? 0 : x`) before passing it.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/html-has-lang`,id:`html-has-lang`,source:`react-doctor`,originallyExternal:!0,rule:{id:`html-has-lang`,title:`html element missing lang`,severity:`warn`,recommendation:'Set `<html lang="…">` so screen readers know the page language.',category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/html-label-has-single-control`,id:`html-label-has-single-control`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-label-has-single-control`,title:`Label wraps multiple native controls`,severity:`warn`,recommendation:`Associate each label with one native control; use fieldset and legend to name a group of controls.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-invalid-paragraph-child`,id:`html-no-invalid-paragraph-child`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-invalid-paragraph-child`,title:`Block element inside a paragraph`,severity:`warn`,recommendation:"Swap the `<p>` for a `<div>`, or move the child outside the paragraph.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-invalid-table-nesting`,id:`html-no-invalid-table-nesting`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-invalid-table-nesting`,title:`Invalid table element nesting`,severity:`warn`,recommendation:"Put each table element in its required parent: `<thead>`/`<tbody>`/`<tfoot>` directly inside `<table>`, `<tr>` inside a row group, `<td>`/`<th>` inside `<tr>`. Browsers quietly fix broken table markup, so write it to spec.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-nested-form`,id:`html-no-nested-form`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-nested-form`,title:`Form nested inside another form`,severity:`warn`,recommendation:`Keep forms separate, or make the inner action a button associated with the outer form. HTML does not permit form descendants inside a form.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-no-nested-interactive`,id:`html-no-nested-interactive`,source:`react-doctor`,originallyExternal:!1,rule:{id:`html-no-nested-interactive`,title:`Interactive control contains another focusable control`,severity:`warn`,recommendation:`Move the inner control beside its interactive ancestor so each action has independent semantics and focus behavior.`,category:`Accessibility`,framework:`global`,isScanRule:!1}},{key:`react-doctor/html-xml-lang-mismatch`,id:`html-xml-lang-mismatch`,source:`react-doctor`,originallyExternal:!0,rule:{id:`html-xml-lang-mismatch`,title:`Conflicting document language declarations`,severity:`warn`,recommendation:`Use matching base languages for lang and xml:lang on the root html element.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/iframe-has-title`,id:`iframe-has-title`,source:`react-doctor`,originallyExternal:!0,rule:{id:`iframe-has-title`,title:`iframe missing title`,severity:`warn`,recommendation:"Add a descriptive `title` so screen reader users know what the embedded frame contains.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/iframe-missing-sandbox`,id:`iframe-missing-sandbox`,source:`react-doctor`,originallyExternal:!0,rule:{id:`iframe-missing-sandbox`,title:`iframe missing sandbox attribute`,severity:`warn`,recommendation:'Add `sandbox=""` or a curated value so embedded pages cannot get full access to your site by default.',category:`Security`,framework:`global`,requires:[`react`],matchByOccurrence:!0,isScanRule:!1}},{key:`react-doctor/iframe-title-unique`,id:`iframe-title-unique`,source:`react-doctor`,originallyExternal:!0,rule:{id:`iframe-title-unique`,title:`Frame title is duplicated`,severity:`warn`,recommendation:`Give each frame a concise title that distinguishes its purpose from sibling frames.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/img-redundant-alt`,id:`img-redundant-alt`,source:`react-doctor`,originallyExternal:!0,rule:{id:`img-redundant-alt`,title:`Redundant words in image alt`,severity:`warn`,recommendation:`Do not put 'image' or 'photo' in alt text. Describe what is shown.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/import-metadata-execution-risk`,id:`import-metadata-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`import-metadata-execution-risk`,title:`Imported metadata reaches code execution`,severity:`error`,recommendation:`Parse imported metadata as data with strict schemas; do not evaluate EXIF, manifests, presets, dropped files, or archives.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/ink-ctrl-c-handler-requires-exit-option`,id:`ink-ctrl-c-handler-requires-exit-option`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-ctrl-c-handler-requires-exit-option`,title:`Ctrl-C handler is unreachable`,severity:`error`,recommendation:"Pass `{exitOnCtrlC: false}` to `render()` before handling Ctrl-C with `useInput`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-newline-inside-text`,id:`ink-newline-inside-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-newline-inside-text`,title:`Retired: Ink Newline placement`,severity:`error`,recommendation:"No change is required; Ink supports standalone `<Newline>` elements.",category:`Bugs`,framework:`global`,tags:[`ink`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/ink-no-bare-process-exit`,id:`ink-no-bare-process-exit`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-bare-process-exit`,title:`Process exits before Ink cleanup`,severity:`error`,recommendation:"Use Ink's `useApp().exit()` so Ink restores terminal state.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-direct-raw-mode`,id:`ink-no-direct-raw-mode`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-direct-raw-mode`,title:`Raw mode changed during render`,severity:`error`,recommendation:"Move `useStdin().setRawMode()` to an effect and restore it in cleanup.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-dom-host-elements`,id:`ink-no-dom-host-elements`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-dom-host-elements`,title:`DOM element used in an Ink tree`,severity:`error`,recommendation:"Use Ink primitives such as `<Box>` and `<Text>` instead of DOM host elements.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-dom-router`,id:`ink-no-dom-router`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-dom-router`,title:`DOM router used in an Ink tree`,severity:`error`,recommendation:`Use React Router's memory router APIs in Ink applications.`,category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-focus-in-render`,id:`ink-no-focus-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-focus-in-render`,title:`Ink focus changed during render`,severity:`error`,recommendation:`Move focus-manager calls to an effect or input handler.`,category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-layout-inside-text`,id:`ink-no-layout-inside-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-layout-inside-text`,title:`Layout component nested in Ink Text`,severity:`error`,recommendation:"Move Ink layout components outside `<Text>` and `<Transform>` boundaries.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-live-hooks-in-render-to-string`,id:`ink-no-live-hooks-in-render-to-string`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-live-hooks-in-render-to-string`,title:`Inert input hook used during string rendering`,severity:`error`,recommendation:"Keep input subscriptions out of components used only by `renderToString()`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-measure-element-in-render`,id:`ink-no-measure-element-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-measure-element-in-render`,title:`Ink element measured during render`,severity:`error`,recommendation:`Measure Ink elements in a layout effect, effect, callback ref, or event handler.`,category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-multiple-static`,id:`ink-no-multiple-static`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-multiple-static`,title:`Multiple unconditional Static regions in one render root`,severity:`warn`,recommendation:"Combine unconditional output in one render root into a single `<Static>`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-raw-text`,id:`ink-no-raw-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-raw-text`,title:`Raw text outside Ink Text`,severity:`error`,recommendation:"Wrap terminal text in Ink's `<Text>` component.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-no-repeated-render`,id:`ink-no-repeated-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-no-repeated-render`,title:`Ink render reused before unmount`,severity:`error`,recommendation:"Call Ink `render()` once and update or rerender the returned instance.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-prefer-use-animation`,id:`ink-prefer-use-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-prefer-use-animation`,title:`Animation loop implemented with setInterval`,severity:`warn`,recommendation:"Use Ink's shared `useAnimation()` scheduler and automatic unmount cleanup.",category:`Performance`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-prefer-use-paste`,id:`ink-prefer-use-paste`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-prefer-use-paste`,title:`Paste interpreted through useInput`,severity:`warn`,recommendation:"Use Ink's `usePaste()` for bracketed multi-character paste input.",category:`Bugs`,framework:`global`,tags:[`ink`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/ink-static-is-append-only`,id:`ink-static-is-append-only`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-static-is-append-only`,title:`Static receives a non-append-only collection`,severity:`warn`,recommendation:"Pass `<Static>` a collection whose existing entries never reorder or disappear.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-static-requires-key`,id:`ink-static-requires-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-static-requires-key`,title:`Static item root missing a key`,severity:`error`,recommendation:"Put a stable `key` on the root element returned by `<Static>`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-suspense-requires-concurrent`,id:`ink-suspense-requires-concurrent`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-suspense-requires-concurrent`,title:`Retired: Ink Suspense rendering mode`,severity:`error`,recommendation:`No change is required; Ink can render Suspense fallbacks without concurrency.`,category:`Bugs`,framework:`global`,tags:[`ink`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/ink-use-reactive-window-size`,id:`ink-use-reactive-window-size`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-use-reactive-window-size`,title:`Terminal dimensions read non-reactively`,severity:`warn`,recommendation:"Use Ink's `useWindowSize()` so resize events trigger a render.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-use-string-width-for-cursor`,id:`ink-use-string-width-for-cursor`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-use-string-width-for-cursor`,title:`String length used as terminal column width`,severity:`warn`,recommendation:"Measure terminal columns with `string-width` before calling `setCursorPosition`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-use-suspend-terminal`,id:`ink-use-suspend-terminal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-use-suspend-terminal`,title:`Interactive child process bypasses Ink suspension`,severity:`error`,recommendation:"Run inherited-TTY child processes inside `useApp().suspendTerminal()`.",category:`Bugs`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/ink-valid-aria-semantics`,id:`ink-valid-aria-semantics`,source:`react-doctor`,originallyExternal:!1,rule:{id:`ink-valid-aria-semantics`,title:`Invalid Ink accessibility semantics`,severity:`error`,recommendation:`Use a role supported by Ink and do not label elements hidden from screen readers.`,category:`Accessibility`,framework:`global`,tags:[`ink`],isScanRule:!1}},{key:`react-doctor/insecure-crypto-risk`,id:`insecure-crypto-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`insecure-crypto-risk`,title:`Weak cryptography in security context`,severity:`warn`,recommendation:"Use modern primitives, `crypto.randomBytes` / Web Crypto randomness, and timing-safe comparisons for signatures, digests, tokens, and auth material.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/insecure-session-cookie`,id:`insecure-session-cookie`,source:`react-doctor`,originallyExternal:!1,rule:{id:`insecure-session-cookie`,title:`Auth cookie missing HttpOnly protection`,severity:`warn`,recommendation:"Set auth/session cookies server-side with `httpOnly: true`, `secure: true`, and `sameSite`. Cookies set via `document.cookie` or with `httpOnly: false` are readable by any XSS payload and can be stolen.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/interactive-supports-focus`,id:`interactive-supports-focus`,source:`react-doctor`,originallyExternal:!0,rule:{id:`interactive-supports-focus`,title:`Interactive element not focusable`,severity:`warn`,recommendation:`Add keyboard focus support so users can reach interactive elements without a pointer.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jotai-derived-atom-returns-fresh-object`,id:`jotai-derived-atom-returns-fresh-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jotai-derived-atom-returns-fresh-object`,title:`Derived atom returns fresh object`,severity:`warn`,recommendation:"Split it into one derived atom per field (each compares cleanly with Object.is), or wrap with `selectAtom(source, fn, shallow)` from jotai/utils if you really need one object.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jotai-select-atom-in-render-body`,id:`jotai-select-atom-in-render-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jotai-select-atom-in-render-body`,title:`selectAtom called during render`,severity:`error`,recommendation:"Lift `selectAtom(base, fn)` to module scope, or wrap it: `const atom = useMemo(() => selectAtom(base, fn), [deps])`. Calling it during render makes a new atom every time and re-subscribes forever.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jotai-tq-use-raw-query-atom`,id:`jotai-tq-use-raw-query-atom`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jotai-tq-use-raw-query-atom`,title:`Subscribing to raw query atom`,severity:`warn`,recommendation:"Derive the field you read: `const dataAtom = atom((get) => get(queryAtom).data)`. Subscribing to the whole query atom re-renders on every refetch, focus, or no-op cache hit.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/js-async-reduce-without-awaited-acc`,id:`js-async-reduce-without-awaited-acc`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-async-reduce-without-awaited-acc`,title:`Async reduce drops its accumulator`,severity:`warn`,recommendation:"Await the accumulator first: `const acc = await previous; ...; return acc;`. Seed it with `Promise.resolve(initial)` so the first run also gets a Promise",category:`Performance`,framework:`global`,isScanRule:!1}},{key:`react-doctor/js-batch-dom-css`,id:`js-batch-dom-css`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-batch-dom-css`,title:`Repeated inline style writes`,severity:`warn`,recommendation:`Do all your reads first, then all your writes. Mixing them inside a loop makes the browser recalculate the layout again and again, which is slow`,category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-cache-property-access`,id:`js-cache-property-access`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-cache-property-access`,title:`Repeated property access in a loop`,severity:`warn`,recommendation:"Read the value once into a variable at the top of the loop: `const { x, y } = obj.deeply.nested`",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-cache-storage`,id:`js-cache-storage`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-cache-storage`,title:`Repeated localStorage reads`,severity:`warn`,recommendation:"Read `localStorage`/`sessionStorage` once and reuse the value. Every read has to parse the data again, which is slow",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-combine-iterations`,id:`js-combine-iterations`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-combine-iterations`,title:`Chained array iterations`,severity:`warn`,recommendation:"Combine `.map().filter()` style chains into one pass with `.reduce()` or a `for...of` loop, so you only loop over the list once",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-early-exit`,id:`js-early-exit`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-early-exit`,title:`Deeply nested if statements`,severity:`warn`,recommendation:"Add an early `return` or `continue` so the deep nesting flattens out and you bail as soon as the answer is known",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-flatmap-filter`,id:`js-flatmap-filter`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-flatmap-filter`,title:`.map().filter(Boolean) loops twice`,severity:`warn`,recommendation:"Use `.flatMap(item => condition ? [value] : [])` to change and drop items in one pass, instead of building a throwaway array in between",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-hoist-intl`,id:`js-hoist-intl`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-hoist-intl`,title:`Intl formatter rebuilt each call`,severity:`warn`,recommendation:"Move `new Intl.NumberFormat(...)` to the top of the file or wrap it in `useMemo`. Building one is slow, so don't redo it on every call",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-hoist-regexp`,id:`js-hoist-regexp`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-hoist-regexp`,title:`RegExp built inside a loop`,severity:`warn`,recommendation:"Move `new RegExp(...)` (or large regex literals) to a constant outside the loop so it isn't rebuilt on every pass",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-index-maps`,id:`js-index-maps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-index-maps`,title:`array.find() inside a loop`,severity:`warn`,recommendation:"Build a `Map` once before the loop instead of calling `array.find(...)` inside it",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-length-check-first`,id:`js-length-check-first`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-length-check-first`,title:`Array compare without length check`,severity:`warn`,recommendation:"Check `a.length === b.length && a.every((x, i) => x === b[i])` so arrays of different sizes stop right away",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-min-max-loop`,id:`js-min-max-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-min-max-loop`,title:`sort() to find min or max`,severity:`warn`,recommendation:"Use `Math.min(...array)` or `Math.max(...array)` instead of sorting the whole list just to read the first or last item",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-set-map-lookups`,id:`js-set-map-lookups`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-set-map-lookups`,title:`Array lookup inside a loop`,severity:`warn`,recommendation:"Use a `Set` or `Map` when you check for the same items over and over. `Array.includes`/`find` scans the whole list each time",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/js-tosorted-immutable`,id:`js-tosorted-immutable`,source:`react-doctor`,originallyExternal:!1,rule:{id:`js-tosorted-immutable`,title:`Spread copy before sort()`,severity:`warn`,recommendation:"Use `array.toSorted()` (ES2023) instead of `[...array].sort()` so you sort without copying the array first",category:`Performance`,framework:`global`,disabledWhen:[`react-native`,`pre-es2023`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/jsx-boolean-value`,id:`jsx-boolean-value`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-boolean-value`,title:`Inconsistent boolean prop notation`,severity:`warn`,recommendation:`Use one boolean-attribute style so equivalent true props scan the same across the codebase.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-curly-brace-presence`,id:`jsx-curly-brace-presence`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-curly-brace-presence`,title:`Unnecessary curly braces in JSX`,severity:`warn`,recommendation:`Use one JSX literal style so equivalent markup scans the same without extra JSX noise.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-filename-extension`,id:`jsx-filename-extension`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-filename-extension`,title:`JSX in disallowed file extension`,severity:`warn`,recommendation:`Name JSX files with the configured extension so file names accurately signal which files contain JSX.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-fragments`,id:`jsx-fragments`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-fragments`,title:`Inconsistent fragment syntax`,severity:`warn`,recommendation:`Use one fragment style so identical wrappers do not look different across files.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-handler-names`,id:`jsx-handler-names`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-handler-names`,title:`Inconsistent event handler names`,severity:`warn`,recommendation:"Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-key`,id:`jsx-key`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-key`,title:`Missing key in list`,severity:`error`,recommendation:"Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-max-depth`,id:`jsx-max-depth`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-max-depth`,title:`JSX nested too deeply`,severity:`warn`,recommendation:`Pull deeply nested JSX into smaller components so it's easier to read.`,category:`Maintainability`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/jsx-no-comment-textnodes`,id:`jsx-no-comment-textnodes`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-comment-textnodes`,title:`Comment rendered as JSX text`,severity:`warn`,recommendation:"Wrap JSX comments in `{/* … */}` so users do not see comment text rendered as children.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-constructed-context-values`,id:`jsx-no-constructed-context-values`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-constructed-context-values`,title:`Unstable context provider value`,severity:`warn`,recommendation:"Wrap the context value in `useMemo` or move it outside the component so consumers do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-duplicate-props`,id:`jsx-no-duplicate-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-duplicate-props`,title:`Duplicate props on element`,severity:`error`,recommendation:`Remove or rename one of the duplicate props so the later value does not silently override the earlier one.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-jsx-as-prop`,id:`jsx-no-jsx-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-jsx-as-prop`,title:`JSX element passed as a prop`,severity:`warn`,recommendation:"Move the JSX outside the component or wrap it in `useMemo` so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-new-array-as-prop`,id:`jsx-no-new-array-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-new-array-as-prop`,title:`New array passed as a prop`,severity:`warn`,recommendation:"Wrap the array in `useMemo` or move it outside the component so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-new-function-as-prop`,id:`jsx-no-new-function-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-new-function-as-prop`,title:`New function passed as a prop`,severity:`warn`,recommendation:"Wrap the callback in `useCallback` or move it outside the component so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-new-object-as-prop`,id:`jsx-no-new-object-as-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-new-object-as-prop`,title:`New object passed as a prop`,severity:`warn`,recommendation:"Wrap the object in `useMemo` or move it outside the component so memoized children do not redraw every render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/jsx-no-script-url`,id:`jsx-no-script-url`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-script-url`,title:`javascript: URL in JSX`,severity:`error`,recommendation:"Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",category:`Security`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-target-blank`,id:`jsx-no-target-blank`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-target-blank`,title:`Unsafe target=_blank link`,severity:`warn`,recommendation:'Add `rel="noreferrer"` (or `"noopener"`) when using `target="_blank"`.',category:`Security`,framework:`global`,requires:[`react`,`target-blank-needs-explicit-protection`],isScanRule:!1}},{key:`react-doctor/jsx-no-undef`,id:`jsx-no-undef`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-undef`,title:`Undefined JSX component`,severity:`error`,recommendation:`Import the component or fix the typo so React can resolve the JSX identifier at runtime.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-no-useless-fragment`,id:`jsx-no-useless-fragment`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-no-useless-fragment`,title:`Unnecessary React fragment`,severity:`warn`,recommendation:`Drop the fragment when it wraps a single child or sits directly under an HTML tag.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-numeric-and-leaked-render`,id:`jsx-numeric-and-leaked-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jsx-numeric-and-leaked-render`,title:`Numeric && renders a stray 0`,severity:`warn`,recommendation:"In `{items.length && <List/>}` React renders a literal `0` when the count is 0. Compare explicitly (`items.length > 0 && <List/>`) or use a ternary (`items.length ? <List/> : null`).",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/jsx-pascal-case`,id:`jsx-pascal-case`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-pascal-case`,title:`Component name not PascalCase`,severity:`warn`,recommendation:`Rename custom JSX components to PascalCase so React treats them as components, not intrinsic DOM tags.`,category:`Maintainability`,framework:`global`,requires:[`react`],tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jsx-props-no-spread-multi`,id:`jsx-props-no-spread-multi`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-props-no-spread-multi`,title:`Same prop spread multiple times`,severity:`warn`,recommendation:`Spread each value at most once so later props cannot silently override earlier props from the same object.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/jsx-props-no-spreading`,id:`jsx-props-no-spreading`,source:`react-doctor`,originallyExternal:!0,rule:{id:`jsx-props-no-spreading`,title:`Props spread onto element`,severity:`warn`,recommendation:`List each prop explicitly so consumers can see the component API instead of receiving hidden spread props.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/jwt-insecure-verification`,id:`jwt-insecure-verification`,source:`react-doctor`,originallyExternal:!1,rule:{id:`jwt-insecure-verification`,title:`JWT verified with the 'none' algorithm`,severity:`error`,recommendation:"Never accept the `none` algorithm; it disables signature verification and lets any forged token through. Pin the real algorithm(s) explicitly (`jwt.verify(token, key, { algorithms: ['RS256'] })`).",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/key-lifecycle-risk`,id:`key-lifecycle-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`key-lifecycle-risk`,title:`Long-lived key material in repository`,severity:`error`,recommendation:`Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/label-has-associated-control`,id:`label-has-associated-control`,source:`react-doctor`,originallyExternal:!0,rule:{id:`label-has-associated-control`,title:`Label missing associated control`,severity:`warn`,recommendation:"Tie every label to a control with `htmlFor`, or by nesting the input.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/lang`,id:`lang`,source:`react-doctor`,originallyExternal:!0,rule:{id:`lang`,title:`Invalid lang attribute value`,severity:`warn`,recommendation:"Use a valid language code like `en` or `en-US` so screen readers choose the right pronunciation rules.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/loading-action-preserves-trigger`,id:`loading-action-preserves-trigger`,source:`react-doctor`,originallyExternal:!1,rule:{id:`loading-action-preserves-trigger`,title:`Loading state removes its initiating action`,severity:`warn`,recommendation:`Keep the initiating control mounted while work is pending. Disable it or mark it busy, and put the spinner or status inside the same control so focus and action identity remain stable.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/local-rpc-native-bridge-risk`,id:`local-rpc-native-bridge-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`local-rpc-native-bridge-risk`,title:`Weak localhost native bridge boundary`,severity:`warn`,recommendation:`Use exact origin allowlists after URL parsing, per-request nonces, narrow methods, and never expose install/update commands to arbitrary web pages.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/mcp-tool-capability-risk`,id:`mcp-tool-capability-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mcp-tool-capability-risk`,title:`MCP tool exposes dangerous capability`,severity:`warn`,recommendation:`MCP tool calls run with the connecting client's authority. Validate inputs, enforce per-tool authorization, and avoid raw filesystem/shell/network access where possible.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/mdx-ssr-execution-risk`,id:`mdx-ssr-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mdx-ssr-execution-risk`,title:`Server-rendered MDX can execute code`,severity:`warn`,recommendation:`Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/media-has-caption`,id:`media-has-caption`,source:`react-doctor`,originallyExternal:!0,rule:{id:`media-has-caption`,title:`Media missing captions`,severity:`warn`,recommendation:'Add `<track kind="captions">` inside every `<audio>` / `<video>`.',category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/mobx-no-make-auto-observable-in-inheritance`,id:`mobx-no-make-auto-observable-in-inheritance`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mobx-no-make-auto-observable-in-inheritance`,title:`Unsupported MobX auto-observable inheritance`,severity:`error`,recommendation:"Replace inheritance with composition, or annotate inherited members explicitly with `makeObservable`.",category:`Bugs`,framework:`global`,requires:[`mobx:6`],isScanRule:!1}},{key:`react-doctor/mobx-no-observer-wrapped-memo`,id:`mobx-no-observer-wrapped-memo`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mobx-no-observer-wrapped-memo`,title:`Invalid MobX observer wrapper order`,severity:`error`,recommendation:"Pass the component directly to `observer`, or apply React `memo` outside the resulting observer component.",category:`Bugs`,framework:`global`,requires:[`mobx:4`,`mobx-react-binding-observer-memo-guard`,`react`],isScanRule:!1}},{key:`react-doctor/mobx-reaction-disposer-discarded`,id:`mobx-reaction-disposer-discarded`,source:`react-doctor`,originallyExternal:!1,rule:{id:`mobx-reaction-disposer-discarded`,title:`MobX reaction disposer discarded`,severity:`warn`,recommendation:"Keep the disposer returned by `reaction` or `autorun` and invoke it during teardown.",category:`Bugs`,framework:`global`,requires:[`mobx:4`],isScanRule:!1}},{key:`react-doctor/motion-animate-presence-must-outlive-child`,id:`motion-animate-presence-must-outlive-child`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-animate-presence-must-outlive-child`,title:`AnimatePresence unmounts with its exiting child`,severity:`warn`,recommendation:`Keep AnimatePresence mounted and place the condition around its child so Motion can observe and animate the child leaving.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-animate-presence-requires-key`,id:`motion-animate-presence-requires-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-animate-presence-requires-key`,title:`AnimatePresence child is missing a key`,severity:`warn`,recommendation:`Give every direct AnimatePresence child a stable key derived from its identity so Motion can track which element exits.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-animate-presence-wait-single-child`,id:`motion-animate-presence-wait-single-child`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-animate-presence-wait-single-child`,title:`AnimatePresence wait mode has multiple children`,severity:`warn`,recommendation:'Render one direct child at a time in `mode="wait"`, or use `sync` or `popLayout` when several children can be present.',category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-create-in-render`,id:`motion-create-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-create-in-render`,title:`Motion component is created during render`,severity:`warn`,recommendation:`Create Motion components at module scope, or memoize a genuinely dynamic component factory so its identity remains stable.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-drag-axis-constraint-mismatch`,id:`motion-drag-axis-constraint-mismatch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-drag-axis-constraint-mismatch`,title:`Motion drag constraints do not match the drag axis`,severity:`warn`,recommendation:`Constrain horizontal drags with left or right bounds and vertical drags with top or bottom bounds, keeping paired bounds in ascending order.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-imperative-animation-in-render`,id:`motion-imperative-animation-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-imperative-animation-in-render`,title:`Imperative Motion animation runs during render`,severity:`error`,recommendation:`Start imperative animations from an effect or user interaction, and derive render output declaratively from props and state.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-keyframe-times-mismatch`,id:`motion-keyframe-times-mismatch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-keyframe-times-mismatch`,title:`Motion keyframes and times have different lengths`,severity:`error`,recommendation:`Provide one transition time for each keyframe so Motion can place every value on the animation timeline.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-layout-on-inline-element`,id:`motion-layout-on-inline-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-layout-on-inline-element`,title:`Motion layout animation targets an inline element`,severity:`warn`,recommendation:`Use inline-block, block, flex, or grid so the browser can apply Motion's transform-based layout animation.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-unstable-layout-id-in-iteration`,id:`motion-unstable-layout-id-in-iteration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-unstable-layout-id-in-iteration`,title:`Motion layout ID is unstable in an iteration`,severity:`warn`,recommendation:`Derive repeated layout IDs from stable item identity, or scope each repeated component with a LayoutGroup ID derived from the item.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-use-transform-range-length`,id:`motion-use-transform-range-length`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-use-transform-range-length`,title:`Motion transform ranges have different lengths`,severity:`error`,recommendation:`Give useTransform input and output ranges the same number of entries so every input stop has an output value.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/motion-value-constructor-in-render`,id:`motion-value-constructor-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-value-constructor-in-render`,title:`Motion value is recreated during render`,severity:`warn`,recommendation:`Use useMotionValue inside React components, or create a manual motionValue outside the render path.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/motion-value-subscription-in-render`,id:`motion-value-subscription-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`motion-value-subscription-in-render`,title:`Motion value subscription is created during render`,severity:`error`,recommendation:`Subscribe with useMotionValueEvent, or create the subscription in an effect and return its cleanup function.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/mouse-events-have-key-events`,id:`mouse-events-have-key-events`,source:`react-doctor`,originallyExternal:!0,rule:{id:`mouse-events-have-key-events`,title:`Mouse handler missing focus handler`,severity:`warn`,recommendation:`Pair mouse events with keyboard ones so keyboard users are not left out.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/nextjs-async-client-component`,id:`nextjs-async-client-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-async-client-component`,title:`Async client component`,severity:`error`,recommendation:`Fetch data in a parent Server Component and pass it as props, or use useQuery/useSWR in the client component`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-async-dynamic-api-not-awaited`,id:`nextjs-async-dynamic-api-not-awaited`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-async-dynamic-api-not-awaited`,title:`Synchronous Next.js request API access`,severity:`error`,recommendation:"Await `cookies()`, `headers()`, `draftMode()`, and async route `params` or `searchParams`, or unwrap their promises with React `use()`, before reading properties.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs:15`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-error-boundary-missing-use-client`,id:`nextjs-error-boundary-missing-use-client`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-error-boundary-missing-use-client`,title:`Error boundary missing 'use client'`,severity:`error`,recommendation:"Add `'use client'` at the top of this file. Error boundaries must be Client Components to catch and render fallback UI",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-global-error-missing-html-body`,id:`nextjs-global-error-missing-html-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-global-error-missing-html-body`,title:`global-error.tsx missing <html>/<body>`,severity:`error`,recommendation:"Wrap your error UI in `<html><body>...</body></html>`. The root layout is unmounted when global-error renders",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-image-missing-sizes`,id:`nextjs-image-missing-sizes`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-image-missing-sizes`,title:`next/image fill image is missing sizes`,severity:`warn`,recommendation:"Add `sizes` matching your layout so `next/image` does not assume the largest candidate and make users download oversized images.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-inline-script-missing-id`,id:`nextjs-inline-script-missing-id`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-inline-script-missing-id`,title:`Inline script missing id`,severity:`warn`,recommendation:'Add `id="descriptive-name"` so Next.js can track, deduplicate, and re-execute the script correctly',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-metadata-url-consistency`,id:`nextjs-metadata-url-consistency`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-metadata-url-consistency`,title:`Canonical and Open Graph URLs disagree`,severity:`warn`,recommendation:`Use the same preferred page URL for alternates.canonical and openGraph.url so search and social previews identify one page.`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],isScanRule:!1}},{key:`react-doctor/nextjs-missing-metadata`,id:`nextjs-missing-metadata`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-missing-metadata`,title:`Page missing metadata for search previews`,severity:`warn`,recommendation:"Add metadata or `generateMetadata()` so search engines and social previews get a title and description.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-a-element`,id:`nextjs-no-a-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-a-element`,title:`Plain anchor reloads internal Next.js links`,severity:`warn`,recommendation:"`import Link from 'next/link'` for client-side navigation, prefetching, and preserved scroll position",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-client-fetch-for-server-data`,id:`nextjs-no-client-fetch-for-server-data`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-client-fetch-for-server-data`,title:`Client fetch for server data`,severity:`warn`,recommendation:`Remove 'use client' and fetch directly in the Server Component. No API round-trip, and secrets stay on the server.`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-client-side-redirect`,id:`nextjs-no-client-side-redirect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-client-side-redirect`,title:`Client-side redirect for navigation`,severity:`warn`,recommendation:`Avoid redirects inside useEffect. Use an event handler, middleware, or server-side redirect (App Router: redirect() from next/navigation; Pages Router: getServerSideProps redirect)`,recommendationOverride:`static-export-redirect`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-css-link`,id:`nextjs-no-css-link`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-css-link`,title:`Linked stylesheet bypasses Next.js CSS optimization`,severity:`warn`,recommendation:`Import CSS directly or use CSS Modules so Next.js can bundle, order, and optimize the stylesheet.`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-default-export-in-route-handler`,id:`nextjs-no-default-export-in-route-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-default-export-in-route-handler`,title:`Default export in route handler`,severity:`error`,recommendation:"Replace `export default` with named HTTP method exports because Next.js ignores default exports in `route.ts`.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-edge-og-runtime`,id:`nextjs-no-edge-og-runtime`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-edge-og-runtime`,title:`Edge runtime in OG image route`,severity:`warn`,recommendation:"Remove `export const runtime = 'edge'` from OG image files. The default Node.js runtime supports more fonts and APIs",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-font-link`,id:`nextjs-no-font-link`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-font-link`,title:`Google Fonts loaded via link`,severity:`warn`,recommendation:'`import { Inter } from "next/font/google"` for self-hosting, zero layout shift, and no render-blocking requests',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-google-analytics-script`,id:`nextjs-no-google-analytics-script`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-google-analytics-script`,title:`Manual Google Analytics script blocks optimized loading`,severity:`warn`,recommendation:"Use `import { GoogleAnalytics } from '@next/third-parties/google'` for automatic optimization and smaller bundles.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-head-import`,id:`nextjs-no-head-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-head-import`,title:`next/head in App Router`,severity:`error`,recommendation:"Use the Metadata API because `next/head` is ignored in the App Router and meta tags will not render.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-img-element`,id:`nextjs-no-img-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-img-element`,title:`Plain img ships unoptimized images`,severity:`warn`,recommendation:"Use `next/image` so users get optimized formats, responsive srcsets, and lazy loading instead of oversized image downloads.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-native-script`,id:`nextjs-no-native-script`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-native-script`,title:`Plain script can block Next.js rendering`,severity:`warn`,recommendation:'Use `next/script` with `strategy="afterInteractive"` or `"lazyOnload"` so third-party scripts do not block rendering.',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-polyfill-script`,id:`nextjs-no-polyfill-script`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-polyfill-script`,title:`Redundant polyfill script`,severity:`warn`,recommendation:`Next.js includes polyfills for fetch, Promise, Object.assign, Array.from, and 50+ others automatically`,category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-redirect-in-try-catch`,id:`nextjs-no-redirect-in-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-redirect-in-try-catch`,title:`redirect() inside try-catch`,severity:`warn`,recommendation:"Move `redirect()` or `notFound()` outside the try block, or rethrow in `catch`, because these APIs throw control-flow errors that catch blocks swallow.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-script-in-head`,id:`nextjs-no-script-in-head`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-script-in-head`,title:`next/script inside next/head`,severity:`error`,recommendation:"Move `<Script>` outside of `<Head>`. next/script manages its own placement and ignores head context",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-side-effect-in-get-handler`,id:`nextjs-no-side-effect-in-get-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-side-effect-in-get-handler`,title:`Side effect in GET handler`,severity:`error`,recommendation:`GET requests can be prefetched and are open to CSRF. Move the side effect to a POST handler.`,category:`Security`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-use-search-params-without-suspense`,id:`nextjs-no-use-search-params-without-suspense`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-use-search-params-without-suspense`,title:`useSearchParams without Suspense`,severity:`warn`,recommendation:"Wrap the component using `useSearchParams` in `<Suspense>` so the rest of the page can stay statically rendered.",category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/nextjs-no-vercel-og-import`,id:`nextjs-no-vercel-og-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nextjs-no-vercel-og-import`,title:`@vercel/og import instead of next/og`,severity:`warn`,recommendation:'Use `import { ImageResponse } from "next/og"`; do not import `@vercel/og` directly because Next.js already bundles it.',category:`Bugs`,framework:`nextjs`,requires:[`nextjs`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-access-key`,id:`no-access-key`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-access-key`,title:`accessKey attribute used`,severity:`warn`,recommendation:"Do not use `accessKey`. It conflicts with assistive tech shortcuts.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-adjust-state-on-prop-change`,id:`no-adjust-state-on-prop-change`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-adjust-state-on-prop-change`,title:`State adjusted after a prop changes`,severity:`warn`,recommendation:`Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-all-caps-body-text`,id:`no-all-caps-body-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-all-caps-body-text`,title:`Long body copy is set in all caps`,severity:`warn`,recommendation:`Use sentence case for paragraphs and reserve uppercase styling for short labels.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-arbitrary-px-font-size`,id:`no-arbitrary-px-font-size`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-arbitrary-px-font-size`,title:`Pixel arbitrary font size`,severity:`warn`,recommendation:"Use `rem` for arbitrary font sizes (`text-[0.8125rem]`, not `text-[13px]`) so text scales with the user's root font-size preference. Pixels stay fine for `border-*` / `outline-*`.",category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-aria-hidden-on-body`,id:`no-aria-hidden-on-body`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-aria-hidden-on-body`,title:`Document body hidden from assistive technology`,severity:`error`,recommendation:`Remove aria-hidden from the document body and hide only the intended subtree.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-aria-hidden-on-focusable`,id:`no-aria-hidden-on-focusable`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-aria-hidden-on-focusable`,title:`aria-hidden on focusable element`,severity:`warn`,recommendation:"Remove `aria-hidden` from focusable elements, or stop them being focusable, so keyboard users do not land on content screen readers hide.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-aria-invalid-without-description`,id:`no-aria-invalid-without-description`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-aria-invalid-without-description`,title:`Invalid control has no error description`,severity:`warn`,recommendation:`Connect an invalid control to its error text with aria-describedby or aria-errormessage.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-arithmetic-on-optional-chained-operand`,id:`no-arithmetic-on-optional-chained-operand`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-arithmetic-on-optional-chained-operand`,title:`Multiplicative math on optional-chained value can be NaN`,severity:`warn`,recommendation:"An optional chain is `undefined` when it short-circuits, so `*`/`/`/`%` on it produces `NaN`, which silently corrupts formatting and comparisons. Provide a `?? fallback` or guard the chain root before the arithmetic.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-array-find-result-member-access-without-guard`,id:`no-array-find-result-member-access-without-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-array-find-result-member-access-without-guard`,title:`Unguarded member access on find() result`,severity:`warn`,recommendation:"`Array.prototype.find`/`findLast` return `undefined` when no element matches, so guard the result with optional chaining (`?.`) or a null check before reading a property, indexing, or calling it.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-array-index-as-key`,id:`no-array-index-as-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-array-index-as-key`,title:`Array index used as a key`,severity:`warn`,recommendation:"Use a stable id from the item, like `key={item.id}` or `key={item.slug}`. Index keys break when the list reorders or filters.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-array-index-deref-without-bounds-or-empty-guard`,id:`no-array-index-deref-without-bounds-or-empty-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-array-index-deref-without-bounds-or-empty-guard`,title:`Array index result dereferenced without a guard`,severity:`warn`,recommendation:"An array index read is typed `T` but is `T | undefined` at runtime, so dereferencing it on an empty list, a non-matching regex, or a short split throws. Add a length/emptiness check or optional chaining before the access.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-array-index-key`,id:`no-array-index-key`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-array-index-key`,title:`Array index used as a key`,severity:`warn`,recommendation:"Use a stable `key` from your data so reordered items keep the right state and DOM.",category:`Performance`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-assertive-status`,id:`no-assertive-status`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-assertive-status`,title:`Status live region interrupts assistive technology`,severity:`warn`,recommendation:'Keep `role="status"` polite. Use `role="alert"` only when an interruption is genuinely imperative.',category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-async-effect-callback`,id:`no-async-effect-callback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-async-effect-callback`,title:`Async effect callback`,severity:`warn`,recommendation:"Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-async-event-handler-without-reentry-guard`,id:`no-async-event-handler-without-reentry-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-async-event-handler-without-reentry-guard`,title:`Async mutating handler without re-entry guard`,severity:`warn`,recommendation:"An async onClick/onSubmit handler on a host control that awaits a mutating request and sets state only afterward stays interactive across the await, so a double-click fires the write twice. Add a leading `if (busy) return` guard, or set a flag before the await inside `try` and reset it in `finally` while the control is disabled.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-auto-scrolling-content`,id:`no-auto-scrolling-content`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-auto-scrolling-content`,title:`Content auto-scrolls forever`,severity:`warn`,recommendation:`Keep content still so readers control the pace, or provide an accessible pause control for essential live movement.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-autofocus`,id:`no-autofocus`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-autofocus`,title:`Autofocus on an element`,severity:`warn`,recommendation:"Do not use `autoFocus`. It disorients users on load.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-autoplay-without-muted`,id:`no-autoplay-without-muted`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-autoplay-without-muted`,title:`Autoplaying media without muted`,severity:`warn`,recommendation:"Always pair `autoPlay` with `muted` (and `playsInline`): `<video autoPlay muted loop playsInline />`. If the sound matters, drop `autoPlay` and let users start it.",category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-barrel-import`,id:`no-barrel-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-barrel-import`,title:`Import from a barrel file`,severity:`warn`,recommendation:"Import from the direct path: `import { Button } from './components/Button'` instead of `./components`",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-blocked-paste`,id:`no-blocked-paste`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-blocked-paste`,title:`Paste blocked in an authentication field`,severity:`error`,recommendation:`Allow paste so people can use password managers, verification codes, assistive tools, and copied text without retyping it.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-boolean-toggle-without-functional-update`,id:`no-boolean-toggle-without-functional-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-boolean-toggle-without-functional-update`,title:`Boolean toggle reads a stale value`,severity:`warn`,recommendation:"Toggle boolean state with the functional updater `setX(previous => !previous)` so deferred callbacks always read the latest committed value.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-broken-image-source`,id:`no-broken-image-source`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-broken-image-source`,title:`Image has no usable source`,severity:`warn`,recommendation:`Provide a real image URL or remove the image element until an asset is available.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-call-component-as-function`,id:`no-call-component-as-function`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-call-component-as-function`,title:`Component called as a function`,severity:`warn`,recommendation:"Render components as JSX (`<Component />`), never call them like functions (`Component(props)`). A direct call runs the component outside React and breaks hooks, state, and memoization.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-cascading-set-state`,id:`no-cascading-set-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-cascading-set-state`,title:`Multiple setState calls in one effect`,severity:`warn`,recommendation:`Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-chain-state-updates`,id:`no-chain-state-updates`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-chain-state-updates`,title:`State updates chained through effects`,severity:`warn`,recommendation:`Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations`,category:`Bugs`,framework:`global`,requires:[`react`],disabledWhen:[`react:18`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-children-prop`,id:`no-children-prop`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-children-prop`,title:`Children passed as a prop`,severity:`warn`,recommendation:`Nest children between the tags so the rendered content is visible in JSX and cannot be hidden inside a props object.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-clipped-overlay`,id:`no-clipped-overlay`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-clipped-overlay`,title:`Overlay can be clipped by an overflow container`,severity:`warn`,recommendation:`Render the overlay outside the clipping container, usually through a portal, or remove the clipping overflow.`,category:`Bugs`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-clone-element`,id:`no-clone-element`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-clone-element`,title:`cloneElement makes child props fragile`,severity:`warn`,recommendation:`Pass children or render props instead so parent code does not depend on fragile cloned child props.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-collapse-request-error-to-empty-state`,id:`no-collapse-request-error-to-empty-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-collapse-request-error-to-empty-state`,title:`Request failure rendered as an empty result`,severity:`warn`,recommendation:`Keep request failure separate from an empty successful response, render an error with a retry action, and leave the previous result visible when appropriate.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-collapsed-literal-or-chain-as-value`,id:`no-collapsed-literal-or-chain-as-value`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-collapsed-literal-or-chain-as-value`,title:`All-literal logical chain used as a multi-value expression`,severity:`warn`,recommendation:"Compare against each value separately (or use an array `.includes(x)`) instead of an all-literal `||`/`&&` chain, which short-circuits to its first literal and drops the rest.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-common-root-font`,id:`no-common-root-font`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-common-root-font`,title:`Page root uses a generic default font choice`,severity:`warn`,recommendation:`Choose a typeface that supports the product's voice, or document why the conventional UI font is intentional.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-conflicting-spring-options`,id:`no-conflicting-spring-options`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-conflicting-spring-options`,title:`Motion spring mixes incompatible option modes`,severity:`warn`,recommendation:`Choose either stiffness/damping/mass or duration/bounce for a Motion spring so every configured value takes effect.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-controlled-input-value-without-state-update`,id:`no-controlled-input-value-without-state-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-controlled-input-value-without-state-update`,title:`Controlled input value is a fixed literal`,severity:`warn`,recommendation:"Drive the input's `value` from state (`const [value, setValue] = useState(...)`) that `onChange` updates, or drop `value` if the field is meant to be read-only.",category:`Bugs`,framework:`global`,tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-controlled-selection-focus-effect`,id:`no-controlled-selection-focus-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-controlled-selection-focus-effect`,title:`Controlled selection change steals focus`,severity:`warn`,recommendation:`Move selection-driven focus into the keyboard or pointer navigation path, or prove that the list just opened before focusing the selected item.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-cramped-container-padding`,id:`no-cramped-container-padding`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-cramped-container-padding`,title:`Bounded text container has cramped padding`,severity:`warn`,recommendation:`Give text at least 8px of space inside a visible border or colored surface.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-create-context-in-render`,id:`no-create-context-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-context-in-render`,title:`createContext called during render`,severity:`error`,recommendation:"Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-create-object-url-in-render`,id:`no-create-object-url-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-object-url-in-render`,title:`Object URL is created during render`,severity:`warn`,recommendation:`Create object URLs in an effect or event handler and revoke each URL when it is replaced or no longer needed.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-create-object-url-without-revoke`,id:`no-create-object-url-without-revoke`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-object-url-without-revoke`,title:`createObjectURL without revokeObjectURL`,severity:`warn`,recommendation:"Call `URL.revokeObjectURL(url)` once the object URL is no longer needed (after the download, in a `useEffect` cleanup, or on unmount). An object URL keeps its Blob/File alive for the document lifetime until it is revoked.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-create-ref-in-function-component`,id:`no-create-ref-in-function-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-ref-in-function-component`,title:`createRef in function component`,severity:`warn`,recommendation:"Replace `createRef()` with the `useRef()` hook inside function components and hooks. `createRef` is only for class components.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-create-store-in-render`,id:`no-create-store-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-create-store-in-render`,title:`Store created during render`,severity:`error`,recommendation:`Create stores at module scope so subscribers are not cut off and saved state does not reset every render.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-crushed-letter-spacing`,id:`no-crushed-letter-spacing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-crushed-letter-spacing`,title:`Letter spacing compresses text excessively`,severity:`warn`,recommendation:`Loosen the tracking until each character keeps a distinct, readable shape.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-danger`,id:`no-danger`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-danger`,title:`Raw HTML injection can run unsafe markup`,severity:`warn`,recommendation:`Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.`,category:`Security`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-danger-with-children`,id:`no-danger-with-children`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-danger-with-children`,title:`dangerouslySetInnerHTML with children`,severity:`error`,recommendation:"Use either `children` or `dangerouslySetInnerHTML` so React does not ignore one source of content.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-dark-mode-glow`,id:`no-dark-mode-glow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-dark-mode-glow`,title:`Colored glow on dark background`,severity:`warn`,recommendation:"Use a subtle `box-shadow` in neutral colors for depth, or a faint `border`. Colored glows on dark backgrounds look overdone.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-blur-orb`,id:`no-decorative-blur-orb`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-blur-orb`,title:`Empty blurred color orb decorates the layout`,severity:`warn`,recommendation:`Use structure, imagery, or a restrained surface treatment instead of an oversized blurred color blob.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-grid-background`,id:`no-decorative-grid-background`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-grid-background`,title:`Surface draws a decorative grid background`,severity:`warn`,recommendation:`Reserve coordinate grids for data or spatial interfaces; use a quieter surface for decoration.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-pulse`,id:`no-decorative-pulse`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-pulse`,title:`Stable copy pulses for attention`,severity:`warn`,recommendation:`Reserve pulsing motion for real in-progress feedback. Remove fake blinking cursors and use static hierarchy for decorative emphasis.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-decorative-radial-spotlight`,id:`no-decorative-radial-spotlight`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-decorative-radial-spotlight`,title:`Large decorative radial spotlight`,severity:`warn`,recommendation:`Use product-specific imagery, structure, or a restrained solid surface instead of a large translucent radial glow.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-default-props`,id:`no-default-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-default-props`,title:`defaultProps removed in React 19`,severity:`warn`,recommendation:'React 19 drops `Component.defaultProps` for function components. Set the defaults in the destructured props instead: `function Foo({ size = "md", variant = "primary" })` instead of `Foo.defaultProps = { size: "md", variant: "primary" }`.',category:`Maintainability`,framework:`global`,requires:[`react:19`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-default-purple-page-gradient`,id:`no-default-purple-page-gradient`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-default-purple-page-gradient`,title:`Page root uses a default purple-spectrum gradient`,severity:`warn`,recommendation:`Build the page palette from the product's identity instead of a conventional purple-to-bright gradient.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-default-warm-page-surface`,id:`no-default-warm-page-surface`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-default-warm-page-surface`,title:`Page defaults to a warm off-white surface`,severity:`warn`,recommendation:`Choose the page surface from an intentional product palette rather than a generic warm neutral default.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-deprecated-keyboard-event-keycode-which`,id:`no-deprecated-keyboard-event-keycode-which`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-deprecated-keyboard-event-keycode-which`,title:`Deprecated KeyboardEvent keyCode or which`,severity:`warn`,recommendation:"`KeyboardEvent.keyCode`/`which`/`charCode` are deprecated and layout/engine dependent for character keys. Branch on `event.key` (logical key like `'/'`) or `event.code` (physical position) so the handler works across keyboard layouts and browsers.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-deprecated-tailwind-class`,id:`no-deprecated-tailwind-class`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-deprecated-tailwind-class`,title:`Deprecated Tailwind v4 utility`,severity:`warn`,recommendation:"Tailwind v4 renamed these utilities: `bg-gradient-*` → `bg-linear-*`, `flex-shrink-*` → `shrink-*`, `flex-grow-*` → `grow-*`, `overflow-ellipsis` → `text-ellipsis`. Use the new names.",category:`Maintainability`,framework:`global`,requires:[`tailwind:4`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-derived-state`,id:`no-derived-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-derived-state`,title:`Derived value copied into state`,severity:`warn`,recommendation:`Work out the value while rendering (or with useMemo if it's expensive) instead of copying it into state and synchronizing it during render or through an effect. See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-derived-state-effect`,id:`no-derived-state-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-derived-state-effect`,title:`Derived state stored in an effect`,severity:`warn`,recommendation:"Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-derived-useState`,id:`no-derived-useState`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-derived-useState`,title:`Prop derived into useState`,severity:`warn`,recommendation:"Compute the value inline so prop changes do not leave `useState` holding a stale copy.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-did-mount-set-state`,id:`no-did-mount-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-did-mount-set-state`,title:`setState in componentDidMount`,severity:`warn`,recommendation:"Setting state in `componentDidMount` triggers an extra render. Use `getDerivedStateFromProps` or initial state instead.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-did-update-set-state`,id:`no-did-update-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-did-update-set-state`,title:`setState in componentDidUpdate`,severity:`warn`,recommendation:"Setting state in `componentDidUpdate` causes another render and can loop. Use `getDerivedStateFromProps` instead.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-direct-mutation-state`,id:`no-direct-mutation-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-direct-mutation-state`,title:`Direct mutation of this.state`,severity:`error`,recommendation:"Don't change `this.state` by hand. `setState()` overwrites it anyway, so always go through `setState()`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-direct-state-mutation`,id:`no-direct-state-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-direct-state-mutation`,title:`State mutated in place`,severity:`warn`,recommendation:"Call the setter with a brand new value instead: `setItems([...items, newItem])`, `setItems(items.filter(x => x !== target))`, or `setItems(items.toSorted(...))`. React only redraws when the value is new, so changing it in place does nothing.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-disabled-zoom`,id:`no-disabled-zoom`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-disabled-zoom`,title:`Zoom disabled on viewport`,severity:`error`,recommendation:"Remove `user-scalable=no` and `maximum-scale` from the viewport meta tag. If the layout breaks at 200% zoom, fix the layout instead.",category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-distracting-elements`,id:`no-distracting-elements`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-distracting-elements`,title:`Distracting marquee or blink element`,severity:`error`,recommendation:"Replace `<marquee>` and `<blink>` with normal markup so motion does not distract or disorient users.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-document-start-view-transition`,id:`no-document-start-view-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-document-start-view-transition`,title:`Direct document.startViewTransition call`,severity:`warn`,recommendation:`Render a <ViewTransition> component and update inside startTransition or useDeferredValue, and React calls startViewTransition for you.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-document-write`,id:`no-document-write`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-document-write`,title:`document.write/writeln`,severity:`warn`,recommendation:"Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",category:`Performance`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-duplicate-static-id-reference`,id:`no-duplicate-static-id-reference`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-duplicate-static-id-reference`,title:`Referenced ID is duplicated`,severity:`error`,recommendation:`Give every statically referenced element a unique id within the rendered JSX tree.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-dynamic-import-path`,id:`no-dynamic-import-path`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-dynamic-import-path`,title:`Non-static dynamic import path`,severity:`warn`,recommendation:"Use a plain string path: `import('./feature/heavy.js')` so the bundler can split this into its own chunk.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-dynamic-tailwind-class-fragment`,id:`no-dynamic-tailwind-class-fragment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-dynamic-tailwind-class-fragment`,title:`Tailwind utility is assembled dynamically`,severity:`warn`,recommendation:`Use complete, statically discoverable Tailwind utility strings for each state instead of interpolating part of a class token.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-eager-new-in-use-state-initializer`,id:`no-eager-new-in-use-state-initializer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-eager-new-in-use-state-initializer`,title:`Eager constructor in useState initializer`,severity:`warn`,recommendation:"Wrap nontrivial construction in a function (`useState(() => new X())`) so it only runs on the first render.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-ease-in-motion`,id:`no-ease-in-motion`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ease-in-motion`,title:`UI motion starts with ease-in`,severity:`warn`,recommendation:`Use ease-out for entrances and exits, or ease-in-out when an element remains visible while moving.`,category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-effect-chain`,id:`no-effect-chain`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-chain`,title:`Effects chained together`,severity:`warn`,recommendation:"Compute as much as possible during render (e.g. `const isGameOver = round > 5`) and write all related state inside the event handler that originally fires the chain. Each effect link adds an extra render and makes the code rigid as requirements evolve",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-effect-event-handler`,id:`no-effect-event-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-event-handler`,title:`Effect used as an event handler`,severity:`warn`,recommendation:`Move event logic into the handler that starts it so the side effect does not run late after an extra render.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-effect-event-in-deps`,id:`no-effect-event-in-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-event-in-deps`,title:`Effect Event listed in effect deps`,severity:`error`,recommendation:`Call the useEffectEvent function inside the effect body and don't list it in the deps. It's non-reactive on purpose, so it must be omitted.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-effect-with-fresh-deps`,id:`no-effect-with-fresh-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-with-fresh-deps`,title:`Effect dependency recreated every render`,severity:`error`,recommendation:`Move the value inside the hook body and depend on its simple inputs instead, or wrap it in useMemo / useCallback so it stays the same between renders.`,category:`Bugs`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],isScanRule:!1}},{key:`react-doctor/no-effect-wrapper-discards-callback-cleanup-return`,id:`no-effect-wrapper-discards-callback-cleanup-return`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-effect-wrapper-discards-callback-cleanup-return`,title:`Effect wrapper discards forwarded cleanup return`,severity:`warn`,recommendation:"A custom effect wrapper must return its forwarded EffectCallback's result so React can run the cleanup. Calling it as a bare `fn()` instead of `return fn()` silently drops the cleanup, leaking every subscription/timer/listener it set up.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-emoji-heading-decoration`,id:`no-emoji-heading-decoration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-emoji-heading-decoration`,title:`Heading uses emoji as decoration`,severity:`warn`,recommendation:`Use purposeful product artwork or a consistent icon system outside the heading instead of decorating heading copy with platform-dependent emoji.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-empty-card-shell`,id:`no-empty-card-shell`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-empty-card-shell`,title:`Empty semantic element renders as a card`,severity:`warn`,recommendation:`Render meaningful content or remove the bordered, rounded shell until content exists.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-enter-submit-without-ime-composition-guard`,id:`no-enter-submit-without-ime-composition-guard`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-enter-submit-without-ime-composition-guard`,title:`Enter submit without IME composition guard`,severity:`warn`,recommendation:"Bail on IME composition before acting on Enter: `if (e.nativeEvent.isComposing) return;` (or track composition with `onCompositionStart`/`onCompositionEnd`). Otherwise Enter fires mid-composition and commits a half-typed value for CJK users.",category:`Bugs`,framework:`global`,requires:[`i18n`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-eval`,id:`no-eval`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-eval`,title:`eval() runs untrusted code strings`,severity:`error`,recommendation:"Use `JSON.parse` for data, or rewrite the code so it doesn't build and run code from strings.",category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-event-handler`,id:`no-event-handler`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-event-handler`,title:`Event logic handled in an effect`,severity:`warn`,recommendation:`Run the side effect in the event handler that triggers it, instead of watching its state from a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-event-trigger-state`,id:`no-event-trigger-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-event-trigger-state`,title:`State exists only to trigger an effect`,severity:`warn`,recommendation:"Delete the trigger state (`useState(null)` plus the `useEffect` that watches it) and call the side effect like `post(...)`, `navigate(...)`, or `track(...)` directly in the event handler that set it. State shouldn't exist just to kick off an effect.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-excessive-card-surfaces`,id:`no-excessive-card-surfaces`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-card-surfaces`,title:`Page boxes too many groups into cards`,severity:`warn`,recommendation:`Let related content share the page surface and use spacing, dividers, or typography before adding another card boundary.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-centered-copy`,id:`no-excessive-centered-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-centered-copy`,title:`Page centers multiple body-copy blocks`,severity:`warn`,recommendation:`Keep longer body copy left-aligned and reserve centered text for one concise focal statement.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-font-families`,id:`no-excessive-font-families`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-font-families`,title:`Page mixes too many font families`,severity:`warn`,recommendation:`Use a display face, a body face, and at most one purposeful outlier such as monospace.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-motion-stagger`,id:`no-excessive-motion-stagger`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-motion-stagger`,title:`Motion child stagger is too long`,severity:`warn`,recommendation:`Keep per-item stagger intervals short so later content does not feel artificially delayed.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-excessive-pill-treatment`,id:`no-excessive-pill-treatment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-excessive-pill-treatment`,title:`Page overuses pill-shaped treatments`,severity:`warn`,recommendation:`Use pills only for controls or compact metadata whose shape communicates a real role.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-fake-browser-chrome`,id:`no-fake-browser-chrome`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fake-browser-chrome`,title:`Preview redraws decorative browser chrome`,severity:`warn`,recommendation:`Show the product screenshot directly or use a purposeful frame instead of adding imitation browser controls.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-fetch-in-effect`,id:`no-fetch-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fetch-in-effect`,title:`Data fetching inside an effect`,severity:`warn`,recommendation:"Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-fetch-response-used-without-status-check`,id:`no-fetch-response-used-without-status-check`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fetch-response-used-without-status-check`,title:`fetch Response consumed without status check`,severity:`warn`,recommendation:"Check `response.ok` (or `response.status`) before consuming a `fetch` Response with `.json()`/`.text()`/`.blob()`. `fetch` resolves on HTTP 4xx/5xx, so an unchecked response parses the error body as success or crashes on an always-truthy guard.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-fill-map-element-as-key`,id:`no-fill-map-element-as-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fill-map-element-as-key`,title:`fill().map() first param is the element, not the index`,severity:`warn`,recommendation:"After `.fill(value)` every element is identical, so a lone `.map((n) => ...)` binds `n` to that value (whatever the parameter is named) and gives every child the same key. Add the index as the second parameter: `.map((_, index) => ...)`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-find-dom-node`,id:`no-find-dom-node`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-find-dom-node`,title:`findDOMNode breaks component encapsulation`,severity:`warn`,recommendation:"Use a ref to reach DOM nodes because `findDOMNode` was removed in React 19 and can crash the app.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-fixed-inside-transformed-ancestor`,id:`no-fixed-inside-transformed-ancestor`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-fixed-inside-transformed-ancestor`,title:`Fixed element is scoped by a transformed ancestor`,severity:`warn`,recommendation:`Move viewport-fixed UI outside transformed ancestors, or intentionally use absolute positioning within that containing block.`,category:`Bugs`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-flat-page-type-scale`,id:`no-flat-page-type-scale`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-flat-page-type-scale`,title:`Page typography uses a compressed size range`,severity:`warn`,recommendation:`Use a clearer size hierarchy when a page declares several explicit typography steps.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-floating-then-in-jsx-handler`,id:`no-floating-then-in-jsx-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-floating-then-in-jsx-handler`,title:`Floating .then in a JSX event handler`,severity:`warn`,recommendation:"A `.then()` chain with no `.catch` in an event handler becomes an uncaught promise rejection no error boundary can catch; add a `.catch` handler (or make the handler `async` and `try/catch`).",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-flush-sync`,id:`no-flush-sync`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-flush-sync`,title:`flushSync skips View Transitions`,severity:`warn`,recommendation:`flushSync forces an immediate update that skips View Transitions and concurrent rendering. Use startTransition for updates that are not urgent.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-focus-in-animation-completion-handler`,id:`no-focus-in-animation-completion-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-focus-in-animation-completion-handler`,title:`Focus waits for animation completion`,severity:`warn`,recommendation:`Move focus when the interaction state changes, independently of visual animation completion, so canceled or reduced animations cannot delay or suppress keyboard focus.`,category:`Accessibility`,framework:`global`,tags:[`design`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-focusable-content-in-aria-hidden`,id:`no-focusable-content-in-aria-hidden`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-focusable-content-in-aria-hidden`,title:`aria-hidden subtree contains focusable content`,severity:`warn`,recommendation:`Remove focusable descendants from aria-hidden content, or hide and disable the whole subtree together.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-focusable-content-in-role-text`,id:`no-focusable-content-in-role-text`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-focusable-content-in-role-text`,title:`Focusable control inside role=text`,severity:`warn`,recommendation:`Move interactive descendants outside role=text, or remove role=text so their semantics remain available.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-full-lodash-import`,id:`no-full-lodash-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-full-lodash-import`,title:`Full lodash import`,severity:`warn`,recommendation:"Import just the function you need: `import debounce from 'lodash/debounce'`. Saves about 70kb.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-full-viewport-centered-hero`,id:`no-full-viewport-centered-hero`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-full-viewport-centered-hero`,title:`Hero uses a full-viewport centered template`,severity:`warn`,recommendation:`Give the opening section a content-driven height and a composition tied to the product instead of centering a headline in an empty viewport.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-full-viewport-width`,id:`no-full-viewport-width`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-full-viewport-width`,title:`Full viewport width causes overflow`,severity:`warn`,recommendation:"Prefer `w-full` (`width: 100%`) over `w-screen` / `100vw`. `100vw` ignores the scrollbar gutter and overflows horizontally.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-generic-handler-names`,id:`no-generic-handler-names`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-generic-handler-names`,title:`Vague event handler name`,severity:`warn`,recommendation:"Rename it to say what it does. For example `handleSubmit` could be `saveUserProfile`, and `handleClick` could be `toggleSidebar`.",category:`Maintainability`,framework:`global`,tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-generic-marketing-copy`,id:`no-generic-marketing-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-generic-marketing-copy`,title:`Page uses generic marketing language`,severity:`warn`,recommendation:`Replace broad promotional phrases with concrete capabilities, outcomes, or evidence.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-generic-purple-blue-icon-gradient`,id:`no-generic-purple-blue-icon-gradient`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-generic-purple-blue-icon-gradient`,title:`Compact icon tile uses a generic purple-to-blue gradient`,severity:`warn`,recommendation:`Use a product color, neutral surface, or unboxed icon instead of a generic purple-to-blue gradient tile.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-giant-component`,id:`no-giant-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-giant-component`,title:`Large component is hard to read and change`,severity:`warn`,recommendation:`Pull each section into its own component so the parent is easier to read, test, and change.`,category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-global-css-variable-animation`,id:`no-global-css-variable-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-global-css-variable-animation`,title:`Animating a global CSS variable`,severity:`error`,recommendation:"Set the variable on the element that needs it instead of a parent, or use `@property` with `inherits: false`. Better yet, update `element.style.transform` directly",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-gradient-text`,id:`no-gradient-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-gradient-text`,title:`Gradient text is hard to read`,severity:`warn`,recommendation:`Use a solid text color so it stays readable. For emphasis, change the weight, size, or color instead of using a gradient.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-gray-on-colored-background`,id:`no-gray-on-colored-background`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-gray-on-colored-background`,title:`Gray text on colored background`,severity:`warn`,recommendation:`Use white or near-white text, or a darker shade of the background color. Gray text on colored backgrounds looks washed out.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hairline-border-wide-shadow`,id:`no-hairline-border-wide-shadow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hairline-border-wide-shadow`,title:`Hairline border is paired with a diffuse shadow`,severity:`warn`,recommendation:`Choose either a defined edge or soft elevation instead of emphasizing both.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hero-eyebrow-chip`,id:`no-hero-eyebrow-chip`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hero-eyebrow-chip`,title:`Hero uses a decorative eyebrow label`,severity:`warn`,recommendation:`Integrate the context into the headline or navigation instead of adding a generic chip above the hero title.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hover-only-reveal`,id:`no-hover-only-reveal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hover-only-reveal`,title:`Content is revealed only on hover`,severity:`warn`,recommendation:`Mirror hover reveals with focus or focus-within, and keep essential controls available to touch users.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-hydration-branch-on-browser-global`,id:`no-hydration-branch-on-browser-global`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-hydration-branch-on-browser-global`,title:`Server and client render different branches`,severity:`error`,recommendation:`Render the same initial output on the server and client, then switch after mount or use useSyncExternalStore with a stable server snapshot.`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-icon-tile-heading-stack`,id:`no-icon-tile-heading-stack`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-icon-tile-heading-stack`,title:`Card stacks an icon tile above its heading`,severity:`warn`,recommendation:`Place the icon beside the heading or let it sit in the content flow without another boxed surface.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-image-hover-transform`,id:`no-image-hover-transform`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-image-hover-transform`,title:`Image scales or rotates on hover`,severity:`warn`,recommendation:`Keep the image stable, or use a subtler hover response tied to an actual interaction affordance.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-img-lazy-with-high-fetchpriority`,id:`no-img-lazy-with-high-fetchpriority`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-img-lazy-with-high-fetchpriority`,title:`Lazy image with high fetchPriority`,severity:`warn`,recommendation:'Don\'t combine `loading="lazy"` with `fetchPriority="high"`. A high-priority image (usually the LCP) should load eagerly; a lazy image is by definition not high priority.',category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-img-without-dimensions`,id:`no-img-without-dimensions`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-img-without-dimensions`,title:`Image has no reserved layout space`,severity:`warn`,recommendation:`Add width and height attributes, or reserve the image's aspect ratio with an explicit CSS box before it loads.`,category:`Performance`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-impure-call-at-module-scope`,id:`no-impure-call-at-module-scope`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-impure-call-at-module-scope`,title:`Nondeterministic built-in at module scope`,severity:`warn`,recommendation:"`Math.random()`, `Date.now()`, `performance.now()`, and `new Date()` run once at module load, so the value is frozen for the whole server process. Move the call into a function/component so it evaluates per request.",category:`Bugs`,framework:`global`,requires:[`ssr`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-impure-state-updater`,id:`no-impure-state-updater`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-impure-state-updater`,title:`State updater has side effects`,severity:`error`,recommendation:`Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-indeterminate-attribute`,id:`no-indeterminate-attribute`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-indeterminate-attribute`,title:`Indeterminate checkbox state set as an attribute`,severity:`warn`,recommendation:"Assign the checkbox element's `indeterminate` DOM property, usually through a ref, because the HTML attribute does not control its visual state.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-inert-pointer-affordance`,id:`no-inert-pointer-affordance`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inert-pointer-affordance`,title:`Pointer cursor has no interaction`,severity:`warn`,recommendation:`Remove the pointer cursor or put the affordance on the control that actually handles the interaction.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-inert-sticky-position`,id:`no-inert-sticky-position`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inert-sticky-position`,title:`Sticky positioning has no inset`,severity:`warn`,recommendation:`Set a non-auto inset such as top, bottom, inset-block-start, or its matching Tailwind utility on the sticky axis.`,category:`Bugs`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-initialize-state`,id:`no-initialize-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-initialize-state`,title:`State initialized from a mount effect`,severity:`warn`,recommendation:`Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-inline-bounce-easing`,id:`no-inline-bounce-easing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-bounce-easing`,title:`Bouncy easing animation`,severity:`warn`,recommendation:"Use `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-expo) for a natural finish. Real objects don't bounce.",category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-inline-exhaustive-style`,id:`no-inline-exhaustive-style`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-exhaustive-style`,title:`Large inline style object rebuilds every render`,severity:`warn`,recommendation:`Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-inline-hoc-on-component`,id:`no-inline-hoc-on-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-hoc-on-component`,title:`Function component defined inline inside an HOC call`,severity:`warn`,recommendation:"Extract the inline function into a named base component at module scope and pass the reference to the HOC (`const CardBase = (props) => ...; const Card = withTracking(CardBase);`). This restores rules-of-hooks and exhaustive-deps analysis and gives the component a stable display name.",category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-inline-prop-on-memo-component`,id:`no-inline-prop-on-memo-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-inline-prop-on-memo-component`,title:`Inline prop defeats memo()`,severity:`warn`,recommendation:"Move the inline `() => ...` / `[]` / `{}` to a stable value with useMemo, useCallback, or module scope, so the memoized child stops redrawing on every parent render",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-interactive-element-to-noninteractive-role`,id:`no-interactive-element-to-noninteractive-role`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-interactive-element-to-noninteractive-role`,title:`Interactive element given noninteractive role`,severity:`warn`,recommendation:`Do not give an interactive element a role that says it is not interactive.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-invalid-progress-range`,id:`no-invalid-progress-range`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-invalid-progress-range`,title:`Progress value falls outside its range`,severity:`error`,recommendation:`Keep determinate progress values within a valid positive range so visual and assistive feedback report the same advancement.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-invisible-focus-control`,id:`no-invisible-focus-control`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-invisible-focus-control`,title:`Invisible native control lacks keyboard focus treatment`,severity:`warn`,recommendation:`Reveal the native control on focus or add a visible focus-within ring to the proxy surface around it.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-is-mounted`,id:`no-is-mounted`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-is-mounted`,title:`isMounted lets async callbacks update after unmount`,severity:`warn`,recommendation:"`isMounted` doesn't work in modern React. Track mount state with a ref, or cancel the async work instead.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-italic-serif-display-heading`,id:`no-italic-serif-display-heading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-italic-serif-display-heading`,title:`Display heading combines italic serif styling`,severity:`warn`,recommendation:`Use a roman display face or reserve the italic serif treatment for a smaller editorial accent.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-json-parse-stringify-clone`,id:`no-json-parse-stringify-clone`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-json-parse-stringify-clone`,title:`JSON parse/stringify deep clone`,severity:`warn`,recommendation:"Replace `JSON.parse(JSON.stringify(value))` with `structuredClone(value)`. It is faster and preserves Dates, Maps, Sets, and cyclic references.",category:`Performance`,framework:`global`,disabledWhen:[`react-native`],isScanRule:!1}},{key:`react-doctor/no-jsx-element-type`,id:`no-jsx-element-type`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-jsx-element-type`,title:`No JSX.Element`,severity:`warn`,recommendation:"Replace `JSX.Element` with `React.ReactNode`. `JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-justified-text`,id:`no-justified-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-justified-text`,title:`Justified text without hyphens`,severity:`warn`,recommendation:"Use `text-align: left` for body text. If you must justify, add `hyphens: auto` and `overflow-wrap: break-word`.",category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-large-animated-blur`,id:`no-large-animated-blur`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-large-animated-blur`,title:`Large animated blur`,severity:`warn`,recommendation:`Keep the blur under 10px, or blur a smaller element. Big blurs use a lot more GPU memory as the element grows`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-layout-property-animation`,id:`no-layout-property-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-layout-property-animation`,title:`Animating a layout property`,severity:`error`,recommendation:"Use `transform: translateX()` or `scale()` instead. They animate smoothly without making the browser redo layout or repaint",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-layout-shifting-interaction-state`,id:`no-layout-shifting-interaction-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-layout-shifting-interaction-state`,title:`Interaction state changes layout geometry`,severity:`warn`,recommendation:`Keep hover, focus, and pressed feedback to paint-only or transform properties so nearby content does not move when the state changes.`,category:`Maintainability`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-layout-transition-inline`,id:`no-layout-transition-inline`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-layout-transition-inline`,title:`Animating layout properties`,severity:`warn`,recommendation:"Animate `transform` and `opacity` instead, since they're cheap for the browser. For height, animate `grid-template-rows` from `0fr` to `1fr`.",category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-legacy-class-lifecycles`,id:`no-legacy-class-lifecycles`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-legacy-class-lifecycles`,title:`Legacy class lifecycle methods`,severity:`error`,recommendation:"Move `componentWillMount` work to `componentDidMount`, `componentWillReceiveProps` to `componentDidUpdate` or the static `getDerivedStateFromProps`, and `componentWillUpdate` to `getSnapshotBeforeUpdate` plus `componentDidUpdate`. The `UNSAFE_` prefix only hides the warning. React 19 removes both.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-legacy-context-api`,id:`no-legacy-context-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-legacy-context-api`,title:`Legacy context API`,severity:`error`,recommendation:"Swap `childContextTypes` + `getChildContext` for `const MyContext = createContext(...)` and `<MyContext.Provider value={...}>`. Swap `contextTypes` for `static contextType = MyContext` or `useContext()` in a function component. Move the provider and every consumer together, or some consumers read the wrong context.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-loading-flag-reset-outside-finally`,id:`no-loading-flag-reset-outside-finally`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-loading-flag-reset-outside-finally`,title:`Loading flag reset outside finally`,severity:`warn`,recommendation:"A trailing `setLoading(false)` after an `await` never runs if the awaited call rejects, so the flag stays stuck truthy; reset it in a `finally` block (or mirror the reset on every catch) so it clears on both paths.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-locale-format-in-render`,id:`no-locale-format-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-locale-format-in-render`,title:`Locale/timezone formatting during render`,severity:`warn`,recommendation:`Format locale/timezone-dependent values in a post-mount useEffect + state, or pass an explicit locale and timeZone so the server and the browser render the same text. Only runs on SSR-capable projects.`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-long-transition-duration`,id:`no-long-transition-duration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-long-transition-duration`,title:`Transition duration too long`,severity:`warn`,recommendation:`Keep UI transitions under 1s. Use about 100 to 150ms for instant feedback, 200 to 300ms for state changes, and 300 to 500ms for layout. Save longer ones for big page-load animations.`,category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-low-contrast-inline-style`,id:`no-low-contrast-inline-style`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-low-contrast-inline-style`,title:`Low-contrast text in inline style`,severity:`warn`,recommendation:`Text needs a WCAG contrast ratio of at least 4.5:1 (3:1 for large/bold text) against its background. Darken or lighten one of the colors until it passes.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-manufactured-contrast-copy`,id:`no-manufactured-contrast-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-manufactured-contrast-copy`,title:`Page repeatedly uses manufactured contrast copy`,severity:`warn`,recommendation:`State the value directly instead of repeatedly contrasting it with a vague alternative.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-many-boolean-props`,id:`no-many-boolean-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-many-boolean-props`,title:`Boolean prop combinations are hard to test`,severity:`warn`,recommendation:`Split boolean-heavy APIs into smaller components or named variants so combinations stay testable.`,category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-match-media-in-state-initializer`,id:`no-match-media-in-state-initializer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-match-media-in-state-initializer`,title:`matchMedia in state initializer`,severity:`warn`,recommendation:"Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-mirror-prop-effect`,id:`no-mirror-prop-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mirror-prop-effect`,title:`Prop mirrored into state via effect`,severity:`warn`,recommendation:"Delete both the `useState` and the `useEffect` and read the prop directly while rendering. Copying a prop into state shows the old value on the first render before the effect catches up.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-mixed-animation-owners`,id:`no-mixed-animation-owners`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mixed-animation-owners`,title:`CSS and Motion animate the same property`,severity:`warn`,recommendation:`Give each property one animation owner. Remove its CSS transition when Motion controls it, or move the CSS transition to a different element or property.`,category:`Maintainability`,framework:`global`,tags:[`design`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-mixed-icon-libraries`,id:`no-mixed-icon-libraries`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mixed-icon-libraries`,title:`File mixes visual icon families`,severity:`warn`,recommendation:`Use one icon family in each file so stroke, fill, and proportions agree.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-mixed-srcset-descriptors`,id:`no-mixed-srcset-descriptors`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mixed-srcset-descriptors`,title:`srcSet mixes width and density descriptors`,severity:`warn`,recommendation:`Use either width descriptors with sizes or density descriptors throughout one srcSet, never both.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-moment`,id:`no-moment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-moment`,title:`Using moment.js`,severity:`warn`,recommendation:"Switch to `import { format } from 'date-fns'` or `import dayjs from 'dayjs'` (2kb).",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-monotonous-page-spacing`,id:`no-monotonous-page-spacing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-monotonous-page-spacing`,title:`Page repeats one spacing value throughout`,severity:`warn`,recommendation:`Use deliberate spacing tiers to distinguish local groups, components, and page sections.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-multi-comp`,id:`no-multi-comp`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-multi-comp`,title:`Multiple components in one file`,severity:`warn`,recommendation:`Move secondary components into their own files so each component stays easier to find, test, and change.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-multiple-main-landmarks`,id:`no-multiple-main-landmarks`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-multiple-main-landmarks`,title:`View contains multiple main landmarks`,severity:`warn`,recommendation:`Keep one visible main landmark per rendered view so assistive-technology users can jump to the primary content unambiguously.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-multiple-unlabeled-navigation-landmarks`,id:`no-multiple-unlabeled-navigation-landmarks`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-multiple-unlabeled-navigation-landmarks`,title:`Repeated navigation landmarks need unique names`,severity:`warn`,recommendation:`Give each coexisting navigation landmark a concise, unique aria-label or aria-labelledby value.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-mutable-in-deps`,id:`no-mutable-in-deps`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutable-in-deps`,title:`Mutable value in effect dependencies`,severity:`error`,recommendation:"Read mutable values like `location.pathname` or `ref.current` inside the effect body, or subscribe with `useSyncExternalStore`. Changing them doesn't redraw the screen, so listing them in deps won't make the effect run again.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-mutate-queried-dom-node-in-component`,id:`no-mutate-queried-dom-node-in-component`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutate-queried-dom-node-in-component`,title:`Mutating a queried DOM node this component renders`,severity:`warn`,recommendation:`Drive the node with state/props (or a ref for genuinely uncontrolled nodes) instead of querying it and mutating its style/class. Imperative edits to a node React renders are invisible to the virtual DOM and get reverted or clobbered on the next render.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-mutate-then-set-or-return-same-reference`,id:`no-mutate-then-set-or-return-same-reference`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutate-then-set-or-return-same-reference`,title:`State mutated in place then set by same reference`,severity:`warn`,recommendation:`Copy state before mutating it, then pass the fresh reference to the matching useState setter.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-mutating-array-method-on-prop-or-hook-result`,id:`no-mutating-array-method-on-prop-or-hook-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutating-array-method-on-prop-or-hook-result`,title:`In-place array mutation on a prop or hook result`,severity:`warn`,recommendation:"`sort`, `reverse`, and `splice` mutate in place, so calling them on a prop or hook result corrupts shared state. Use the immutable `toSorted`/`toReversed`/`toSpliced`, or copy the array first (`[...array].sort()`) when targeting pre-ES2023 runtimes.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-mutating-reducer-state`,id:`no-mutating-reducer-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-mutating-reducer-state`,title:`Reducer mutates its state`,severity:`error`,recommendation:`Return a new state object from the reducer instead of changing the old one and returning it. React only notices the change when the object is new.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-namespace`,id:`no-namespace`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-namespace`,title:`Namespaced JSX element`,severity:`warn`,recommendation:"Use a plain component or DOM tag because React cannot render JSX namespaced names like `ns:Foo`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-nested-card-surface`,id:`no-nested-card-surface`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nested-card-surface`,title:`Card surface is nested inside another card`,severity:`warn`,recommendation:`Flatten the inner surface and use spacing, a divider, or typography to communicate the hierarchy.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-nested-component-definition`,id:`no-nested-component-definition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nested-component-definition`,title:`Component defined inside another component`,severity:`warn`,recommendation:`Move it to module scope or a separate file so React does not recreate the component and erase its state on every parent render.`,category:`Bugs`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-non-literal-selector-query-without-try-catch`,id:`no-non-literal-selector-query-without-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-non-literal-selector-query-without-try-catch`,title:`Unguarded querySelector with href-derived selector`,severity:`warn`,recommendation:"`querySelector`/`querySelectorAll`/`matches`/`closest` throw a `DOMException` on an invalid CSS selector, and href/hash fragments are frequently invalid. Wrap the call in try/catch or normalize the value with `CSS.escape`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-non-null-assertion-on-maybe-undefined-result`,id:`no-non-null-assertion-on-maybe-undefined-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-non-null-assertion-on-maybe-undefined-result`,title:`Non-null assertion on a maybe-undefined result`,severity:`warn`,recommendation:"Drop the `!` on `.find`/`.match`/`.get` results and handle the miss (optional chaining, a guard, or a fallback). These built-ins return `undefined`/`null` when nothing matches, so the assertion just moves the crash one line later.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-nondeterministic-id-value-in-render-body`,id:`no-nondeterministic-id-value-in-render-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nondeterministic-id-value-in-render-body`,title:`Nondeterministic id generated in render body`,severity:`warn`,recommendation:`An id generator (uniqueId/nanoid/crypto.randomUUID/shortid) bound in the render body re-runs every render, so the id is unstable and breaks htmlFor/aria/SVG references and SSR hydration. Use useId for reference ids, or a useRef/useState initializer to mint it once.`,category:`Bugs`,framework:`global`,tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-noninteractive-element-interactions`,id:`no-noninteractive-element-interactions`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-noninteractive-element-interactions`,title:`Handler on non-interactive element`,severity:`warn`,recommendation:`Put interactions on a button or link, or add an interactive role.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-noninteractive-element-to-interactive-role`,id:`no-noninteractive-element-to-interactive-role`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-noninteractive-element-to-interactive-role`,title:`Noninteractive element given interactive role`,severity:`warn`,recommendation:`Use a real interactive element instead of adding an interactive role to a static one.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-noninteractive-tabindex`,id:`no-noninteractive-tabindex`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-noninteractive-tabindex`,title:`Tabindex on non-interactive element`,severity:`warn`,recommendation:"Only add `tabIndex` to interactive elements or interactive roles.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-nonresizable-textarea`,id:`no-nonresizable-textarea`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nonresizable-textarea`,title:`Textarea resizing is disabled`,severity:`warn`,recommendation:`Let users resize multiline fields in at least the block direction unless the control grows automatically with its content.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-nullish-coalescing-arithmetic-precedence`,id:`no-nullish-coalescing-arithmetic-precedence`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-nullish-coalescing-arithmetic-precedence`,title:`Nullish coalescing swallowed by adjacent arithmetic`,severity:`warn`,recommendation:"Arithmetic binds tighter than `??`, so wrap the nullish part in parentheses (`(x ?? 0) / y`) to compute the value you actually intend.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-numbered-section-markers`,id:`no-numbered-section-markers`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-numbered-section-markers`,title:`Styled numbers are used as section decoration`,severity:`warn`,recommendation:`Remove decorative section numbering unless the sequence communicates real progress or ordered steps.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-object-keys-values-entries-on-maybe-undefined`,id:`no-object-keys-values-entries-on-maybe-undefined`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-object-keys-values-entries-on-maybe-undefined`,title:`Object.keys/values/entries on maybe-undefined value`,severity:`warn`,recommendation:"`Object.keys`, `Object.values`, and `Object.entries` throw on `undefined`/`null`, so pass a `?? {}` fallback or guard the value with a null check before calling them.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-object-or-array-coerced-to-string-in-template-literal`,id:`no-object-or-array-coerced-to-string-in-template-literal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-object-or-array-coerced-to-string-in-template-literal`,title:`Object or lossy array coerced in a template literal`,severity:`warn`,recommendation:"Interpolating an object or structurally nested array can produce `[object Object]` or lose structure; read a specific value or use `.join`/`JSON.stringify`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-outline-none`,id:`no-outline-none`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-outline-none`,title:`outline:none removes focus ring`,severity:`warn`,recommendation:"Style `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px }` so the focus ring shows for keyboard users but not mouse clicks.",category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-overloaded-hover-state`,id:`no-overloaded-hover-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-overloaded-hover-state`,title:`Hover state stacks too many effects`,severity:`warn`,recommendation:`Use one restrained hover idea, such as a color shift or a small shadow change, instead of stacking motion, color, and depth effects.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-oversized-long-heading`,id:`no-oversized-long-heading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-oversized-long-heading`,title:`Long headline uses an oversized display scale`,severity:`warn`,recommendation:`Reduce the display size for sentence-length headlines, or tighten the copy before using a hero scale.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-overwide-text-measure`,id:`no-overwide-text-measure`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-overwide-text-measure`,title:`Text measure is too wide`,severity:`warn`,recommendation:`Constrain long-form text to a readable line length, usually between 60ch and 75ch.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pass-data-to-parent`,id:`no-pass-data-to-parent`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-pass-data-to-parent`,title:`Data passed to parent via effect`,severity:`warn`,recommendation:`Fetch the data in the parent and pass it down as a prop (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-pass-live-state-to-parent`,id:`no-pass-live-state-to-parent`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-pass-live-state-to-parent`,title:`Live state pushed to parent via effect`,severity:`warn`,recommendation:`Move the state up to the parent (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-passive-request-owner-ref`,id:`no-passive-request-owner-ref`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-passive-request-owner-ref`,title:`Passive owner ref leaves a stale-request window`,severity:`warn`,recommendation:"Invalidate requests when the owner changes before an old promise can settle, or bind the request to lifecycle cleanup instead of trusting a ref synchronized by `useEffect`.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-path-prefix-containment`,id:`no-path-prefix-containment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-path-prefix-containment`,title:`Path containment check uses a string prefix`,severity:`warn`,recommendation:"Use `path.relative(root, candidate)` and reject `..` or absolute results instead of comparing path strings with the bare root prefix.",category:`Security`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-permanent-will-change`,id:`no-permanent-will-change`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-permanent-will-change`,title:`Permanent will-change wastes GPU`,severity:`warn`,recommendation:"Add will-change when the animation starts (`onMouseEnter`) and remove it when it ends (`onAnimationEnd`). Leaving it on all the time wastes GPU memory and can slow things down",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-pill-navigation-count`,id:`no-pill-navigation-count`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pill-navigation-count`,title:`Navigation count uses a pill badge`,severity:`warn`,recommendation:`Align navigation counts as plain tabular text instead of decorating each value as a pill.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-placeholder-only-field`,id:`no-placeholder-only-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-placeholder-only-field`,title:`Field relies on placeholder text for its label`,severity:`warn`,recommendation:`Add a visible label associated with the field. Keep placeholder text for examples or formatting hints.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-placeholder-persona-copy`,id:`no-placeholder-persona-copy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-placeholder-persona-copy`,title:`Page renders a placeholder persona`,severity:`warn`,recommendation:`Use realistic, context-specific sample content or clearly label the surface as a demo instead of shipping generic placeholder identities.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pointer-disabled-enabled-control`,id:`no-pointer-disabled-enabled-control`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pointer-disabled-enabled-control`,title:`Enabled control ignores pointer input`,severity:`warn`,recommendation:"Remove `pointer-events: none` from enabled controls so mouse and touch users can operate them, or mark the control unavailable with native disabled semantics.",category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-polymorphic-children`,id:`no-polymorphic-children`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-polymorphic-children`,title:`Children type checked at runtime`,severity:`warn`,recommendation:"Add clear subcomponents like `<Button.Text>` and `<Button.Icon>` so callers don't have to check `typeof children`.",category:`Maintainability`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-predicate-function-reference-in-boolean-position`,id:`no-predicate-function-reference-in-boolean-position`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-predicate-function-reference-in-boolean-position`,title:`Predicate function used without calling it`,severity:`warn`,recommendation:"A bare `is*`/`has*`/`can*`/`should*`/`will*` function reference is always truthy in a condition, so the guarded branch never behaves as intended. Call the function (`isReady()`) to evaluate the predicate.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-presentation-role-conflict`,id:`no-presentation-role-conflict`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-presentation-role-conflict`,title:`Presentational element exposes conflicting semantics`,severity:`warn`,recommendation:`Remove the presentational role, focusability, or global ARIA state so assistive technologies receive one consistent semantic model.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-prevent-default`,id:`no-prevent-default`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prevent-default`,title:`preventDefault on a form or link`,severity:`warn`,recommendation:"Use `<form action>` where your framework supports it (it works without JS), or use a `<button>` instead of an `<a>` with preventDefault.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-promise-then-side-effect-in-effect-without-catch`,id:`no-promise-then-side-effect-in-effect-without-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-promise-then-side-effect-in-effect-without-catch`,title:`Effect promise .then sets state with no catch`,severity:`warn`,recommendation:"An async init in an effect that sets state in `.then` but has no `.catch` leaves the component stuck and raises an unhandled rejection when it fails. Add a `.catch` on the chain (`.finally` does not handle the rejection).",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-prop-callback-in-effect`,id:`no-prop-callback-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prop-callback-in-effect`,title:`Parent kept in sync with a callback effect`,severity:`warn`,recommendation:`Move the shared state into a Provider so both sides read the same value. Then you don't need a useEffect to keep them in sync.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-prop-callback-in-render`,id:`no-prop-callback-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prop-callback-in-render`,title:`Prop callback invoked during render`,severity:`error`,recommendation:`Invoke the callback from the event or asynchronous operation that produced the value, or from an effect when synchronizing with an external system. Render must stay pure because React can replay or discard it.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-prop-types`,id:`no-prop-types`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-prop-types`,title:`propTypes ignored in React 19`,severity:`warn`,recommendation:"React 19 ignores `Component.propTypes`, so invalid props pass silently. Describe props with TypeScript types and add real runtime checks (or schema parsing) only where data can actually be wrong. Only runs on React 19+ projects.",category:`Maintainability`,framework:`global`,requires:[`react:19`],tags:[`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pulsing-status-dot`,id:`no-pulsing-status-dot`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pulsing-status-dot`,title:`Decorative status dot pulses continuously`,severity:`warn`,recommendation:`Use a static status indicator unless the element communicates real work in progress.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pure-black-background`,id:`no-pure-black-background`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pure-black-background`,title:`Pure black background`,severity:`warn`,recommendation:"Nudge the background slightly toward your brand color, like `#0a0a0f` or Tailwind's `bg-gray-950`. Pure black looks harsh on modern screens.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-pure-black-shadow`,id:`no-pure-black-shadow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-pure-black-shadow`,title:`Surface uses a pure-black shadow`,severity:`warn`,recommendation:`Tint shadows toward the surrounding surface or use a softer neutral token instead of pure black.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-radial-halo`,id:`no-radial-halo`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-radial-halo`,title:`Saturated radial halo on a dark surface`,severity:`warn`,recommendation:`Use restrained surface contrast, product-specific imagery, or a subtler accent instead of a saturated radial halo on a dark page.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-random-key`,id:`no-random-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-random-key`,title:`Random value used as a key`,severity:`error`,recommendation:"Use a stable id from the item itself, like `item.id`, a content hash, or the index when the order never changes. Don't build the key from something that changes every time.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-react-children`,id:`no-react-children`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-react-children`,title:`React.Children is fragile when child shape changes`,severity:`warn`,recommendation:`Pass children as explicit props or render them directly so child shape changes do not break traversal logic.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-react-dom-deprecated-apis`,id:`no-react-dom-deprecated-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-react-dom-deprecated-apis`,title:`Deprecated react-dom APIs break in React 19`,severity:`warn`,recommendation:"Switch the old `react-dom` root API (`render` / `hydrate` / `unmountComponentAtNode`) to `createRoot` / `hydrateRoot` / `root.unmount()` from `react-dom/client`. Replace `findDOMNode` with a ref. `react-dom/test-utils` is gone in React 19, so use `act` from `react` and `fireEvent` / `render` from `@testing-library/react`. Only runs on React 18+ projects.",category:`Maintainability`,framework:`global`,requires:[`react:18`],tags:[`test-noise`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-react19-deprecated-apis`,id:`no-react19-deprecated-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-react19-deprecated-apis`,title:`React 19 API migration can break callers`,severity:`warn`,recommendation:`Replace removed React APIs with their supported React 19 alternatives. Only runs on React 19+ projects.`,category:`Maintainability`,framework:`global`,requires:[`react:19`],tags:[`test-noise`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/no-reduced-motion-content-removal`,id:`no-reduced-motion-content-removal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-reduced-motion-content-removal`,title:`Reduced motion removes meaningful content`,severity:`warn`,recommendation:`Keep the same content and actions available under reduced motion, replacing spatial movement with a static or non-spatial presentation.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-redundant-display-class`,id:`no-redundant-display-class`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-redundant-display-class`,title:`Redundant display utility`,severity:`warn`,recommendation:"Drop the display class that matches the element's default (`block` on a `<div>`, `inline` on a `<span>`). It is pure noise; keep only display changes like `flex`, `grid`, or `hidden`.",category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-redundant-roles`,id:`no-redundant-roles`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-redundant-roles`,title:`Redundant ARIA role`,severity:`warn`,recommendation:"Remove redundant `role` attributes so assistive tech reads the element's native semantics without extra noise.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-redundant-should-component-update`,id:`no-redundant-should-component-update`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-redundant-should-component-update`,title:`Redundant shouldComponentUpdate`,severity:`warn`,recommendation:"Drop `shouldComponentUpdate` (PureComponent already shallow-compares) or extend `React.Component` if custom logic is needed.",category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-redundant-title-tooltip`,id:`no-redundant-title-tooltip`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-redundant-title-tooltip`,title:`Title tooltip repeats visible control text`,severity:`warn`,recommendation:`Remove tooltips that merely repeat a control's visible label.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-ref-callback-cleanup-before-react-19`,id:`no-ref-callback-cleanup-before-react-19`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ref-callback-cleanup-before-react-19`,title:`Ref cleanup requires React 19`,severity:`warn`,recommendation:"React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",category:`Bugs`,framework:`global`,requires:[`react:18`],disabledWhen:[`react:19`],isScanRule:!1}},{key:`react-doctor/no-ref-current-in-render`,id:`no-ref-current-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ref-current-in-render`,title:`Ref mutated during render`,severity:`error`,recommendation:`Move ref writes into an event handler or effect. Render must stay pure because React can replay or discard it. The predictable null-guarded lazy initialization pattern remains supported.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-render-in-render`,id:`no-render-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-render-in-render`,title:`Component rendered by inline function call`,severity:`warn`,recommendation:`Make it a named component rendered as JSX so React can track it and preserve its state.`,category:`Maintainability`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-render-prop-children`,id:`no-render-prop-children`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-render-prop-children`,title:`Render-prop slots make this component hard to extend`,severity:`warn`,recommendation:"Swap `renderXxx` props for child components like `<Modal.Header>` or plain `children`, so the parent doesn't control every slot.",category:`Maintainability`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-render-return-value`,id:`no-render-return-value`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-render-return-value`,title:`Using ReactDOM.render return value`,severity:`warn`,recommendation:"Don't use `ReactDOM.render`'s return value. It's legacy and was removed in React 19.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-repeated-container-text`,id:`no-repeated-container-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-container-text`,title:`A card repeats the same text in distinct slots`,severity:`warn`,recommendation:`Keep repeated status or label copy in the one card slot where it carries the clearest meaning.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-emoji-tiles`,id:`no-repeated-emoji-tiles`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-emoji-tiles`,title:`Page repeats boxed emoji as feature icons`,severity:`warn`,recommendation:`Use a coherent icon set or product-specific artwork instead of repeating platform-dependent emoji tiles.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-glass-surfaces`,id:`no-repeated-glass-surfaces`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-glass-surfaces`,title:`Page repeats glass panels`,severity:`warn`,recommendation:`Reserve translucency for one overlay or focal layer and use quieter opaque surfaces elsewhere.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-hover-scale`,id:`no-repeated-hover-scale`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-hover-scale`,title:`Hover scaling repeats across the page`,severity:`warn`,recommendation:`Reserve hover movement for a specific spatial interaction instead of making unrelated cards and tiles all grow under the pointer.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-kicker-labels`,id:`no-repeated-kicker-labels`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-kicker-labels`,title:`Repeated tracked labels scaffold multiple sections`,severity:`warn`,recommendation:`Use stronger section composition or reserve the decorative kicker treatment for one meaningful moment.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-placeholder-navigation`,id:`no-repeated-placeholder-navigation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-placeholder-navigation`,title:`Navigation repeats placeholder destinations`,severity:`warn`,recommendation:`Replace placeholder destinations with real routes or render non-interactive navigation previews until destinations exist.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeated-section-shells`,id:`no-repeated-section-shells`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeated-section-shells`,title:`Page repeats the same padded section shell`,severity:`warn`,recommendation:`Vary section composition and rhythm instead of wrapping every block in the same large padding and centered max-width container.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-repeating-gradient-decoration`,id:`no-repeating-gradient-decoration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-repeating-gradient-decoration`,title:`Surface uses a repeating gradient texture`,severity:`warn`,recommendation:`Use a deliberate texture asset or a quiet solid surface instead of repeating-gradient decoration.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-reset-all-state-on-prop-change`,id:`no-reset-all-state-on-prop-change`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-reset-all-state-on-prop-change`,title:`All state reset on prop change`,severity:`warn`,recommendation:"Pass the prop as `key` so React resets the component for you when the prop changes, instead of clearing every state value by hand in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-responsive-hidden-accessible-name`,id:`no-responsive-hidden-accessible-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-responsive-hidden-accessible-name`,title:`Responsive styles hide a control's accessible name`,severity:`warn`,recommendation:`Keep an accessible name available at every breakpoint, such as persistent sr-only text or an aria-label on the control.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-scale-from-zero`,id:`no-scale-from-zero`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-scale-from-zero`,title:`Animating scale from zero`,severity:`warn`,recommendation:"Use `initial={{ scale: 0.95, opacity: 0 }}`. Elements should gently shrink and fade, not vanish into a point",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-secrets-in-client-code`,id:`no-secrets-in-client-code`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-secrets-in-client-code`,title:`Secret in client code`,severity:`warn`,recommendation:`Move secrets to server-only code. Anything in client env variables gets shipped to the browser, so it can't hold secrets.`,recommendationOverride:`client-secret`,category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-self-updating-effect`,id:`no-self-updating-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-self-updating-effect`,title:`Effect updates its own dependency`,severity:`warn`,recommendation:`Break the loop: work the value out while rendering, set it in an event handler, or guard the update so it stops changing. See https://react.dev/learn/you-might-not-need-an-effect`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-server-side-image-map`,id:`no-server-side-image-map`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-server-side-image-map`,title:`Server-side image map`,severity:`warn`,recommendation:`Replace isMap with semantic links or a client-side map that exposes each target.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-set-state`,id:`no-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-set-state`,title:`Local class state forbidden`,severity:`warn`,recommendation:`Lift state up or use an external store so class-local state does not hide ownership.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-set-state-after-await-in-effect`,id:`no-set-state-after-await-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-set-state-after-await-in-effect`,title:`State update after await in an effect`,severity:`warn`,recommendation:"In a `useEffect` whose dependencies can change, guard any setter call that runs after an `await` behind a cancellation/ignore flag, or return a cleanup that cancels the async work.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-set-state-in-render`,id:`no-set-state-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-set-state-in-render`,title:`setState called during render`,severity:`warn`,recommendation:"Move the setter into a `useEffect` or an event handler, or compute the value while rendering. Calling a setter during render starts another render that calls it again, looping forever.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-shape-assembled-illustration`,id:`no-shape-assembled-illustration`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-shape-assembled-illustration`,title:`Large illustration is assembled from primitive shapes`,severity:`warn`,recommendation:`Replace the shape pile with deliberate artwork, a photograph, or a purpose-built graphic that supports the product story.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-side-effect-in-state-updater-function`,id:`no-side-effect-in-state-updater-function`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-side-effect-in-state-updater-function`,title:`Side effect inside a state updater function`,severity:`warn`,recommendation:`React may replay a state updater, so callbacks, analytics, and persistence inside it can run more than once. Compute state purely, then perform the side effect outside the setter.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-side-tab-border`,id:`no-side-tab-border`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-side-tab-border`,title:`Thick one-sided stripe`,severity:`warn`,recommendation:`Use a background change, a thin neutral edge, or a subtler surface treatment instead of a thick one-sided stripe.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-skipped-heading-level`,id:`no-skipped-heading-level`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-skipped-heading-level`,title:`Heading hierarchy skips a level`,severity:`warn`,recommendation:`Keep headings in a continuous hierarchy inside a page or article so assistive navigation reflects the visual structure.`,category:`Accessibility`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-small-form-control-text`,id:`no-small-form-control-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-small-form-control-text`,title:`Form control text is smaller than 16px`,severity:`warn`,recommendation:`Use at least 16px text on mobile inputs, selects, and textareas so content remains readable and mobile browsers do not zoom unexpectedly.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-smooth-scroll-without-reduced-motion`,id:`no-smooth-scroll-without-reduced-motion`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-smooth-scroll-without-reduced-motion`,title:`Smooth scrolling ignores reduced motion`,severity:`warn`,recommendation:`Enable smooth scrolling only for users without a reduced-motion preference, and fall back to instant scrolling for everyone else.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-spread-accumulator-in-reduce`,id:`no-spread-accumulator-in-reduce`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-spread-accumulator-in-reduce`,title:`Accumulator spread in reduce is quadratic`,severity:`warn`,recommendation:"Mutate the accumulator and return it (`acc[key] = value; return acc`) so the fold stays O(n) instead of copying the whole accumulator every step.",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-spread-props-over-defaults-clobbers-with-undefined`,id:`no-spread-props-over-defaults-clobbers-with-undefined`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-spread-props-over-defaults-clobbers-with-undefined`,title:`Spread props over defaults can clobber with undefined`,severity:`warn`,recommendation:`An explicit undefined prop can overwrite a default. Reapply defaults with ?? at the merge or use site, or remove undefined keys before spreading props.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-srcset-without-sizes`,id:`no-srcset-without-sizes`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-srcset-without-sizes`,title:`Responsive image omits sizes`,severity:`warn`,recommendation:`Add a sizes attribute that describes the image's rendered width at each responsive breakpoint.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-stale-timer-ref`,id:`no-stale-timer-ref`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-stale-timer-ref`,title:`Cleared timer ref keeps the stale id`,severity:`warn`,recommendation:"Reset the ref right after clearing (`clearTimeout(ref.current); ref.current = null`) so truthiness checks on the ref keep meaning “timer still pending”.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-static-element-interactions`,id:`no-static-element-interactions`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-static-element-interactions`,title:`Interaction on static element`,severity:`warn`,recommendation:"Give clickable static elements a `role`, or use a button or link.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-static-motion-config-never`,id:`no-static-motion-config-never`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-static-motion-config-never`,title:`MotionConfig always ignores reduced motion`,severity:`warn`,recommendation:'Use `reducedMotion="user"`, or derive the value from an explicit user preference instead of permanently disabling reduced-motion support.',category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-string-false-on-boolean-attribute`,id:`no-string-false-on-boolean-attribute`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-string-false-on-boolean-attribute`,title:`String true/false on a boolean attribute`,severity:`warn`,recommendation:'Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled="false"`. A non-empty string is truthy, so `="false"` actually turns the attribute ON.',category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-string-refs`,id:`no-string-refs`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-string-refs`,title:`String refs are legacy and fragile`,severity:`warn`,recommendation:"Use a callback ref or `useRef` so ref ownership is explicit and not tied to legacy string lookup.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-svg-currentcolor-with-fill-class`,id:`no-svg-currentcolor-with-fill-class`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-svg-currentcolor-with-fill-class`,title:`currentColor fights a fill/stroke class`,severity:`warn`,recommendation:'Pick one source of truth: drop the `fill="currentColor"` attribute and keep the `fill-*` class, or use `fill-current` to inherit the text color. Having both means the class silently wins.',category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-symmetric-text-button-padding`,id:`no-symmetric-text-button-padding`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-symmetric-text-button-padding`,title:`Text button uses symmetric padding`,severity:`warn`,recommendation:`Give text buttons more horizontal than vertical padding to preserve their control shape.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-sync-xhr`,id:`no-sync-xhr`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-sync-xhr`,title:`Synchronous XMLHttpRequest`,severity:`warn`,recommendation:"Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",category:`Performance`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-tailwind-layout-transition`,id:`no-tailwind-layout-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tailwind-layout-transition`,title:`Animating a layout property`,severity:`warn`,recommendation:"Animate `transform` and `opacity` instead, since they skip layout and run on the compositor. For height, animate `grid-template-rows` from `0fr` to `1fr`.",category:`Performance`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-this-in-sfc`,id:`no-this-in-sfc`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-this-in-sfc`,title:`this used in function component`,severity:`warn`,recommendation:"Read from the `props` argument because function components do not have a React instance `this`.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-tight-all-caps-heading`,id:`no-tight-all-caps-heading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tight-all-caps-heading`,title:`Long all-caps heading has collision-prone leading`,severity:`warn`,recommendation:`Use at least 1.0 line-height for an all-caps display heading that can wrap, or switch the heading to ordinary casing.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tight-body-leading`,id:`no-tight-body-leading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tight-body-leading`,title:`Body copy has cramped line spacing`,severity:`warn`,recommendation:`Use a line height of at least 1.3 for multi-line body text.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tight-display-tracking`,id:`no-tight-display-tracking`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tight-display-tracking`,title:`Display heading uses the tightest tracking preset`,severity:`warn`,recommendation:`Use normal or moderately tight tracking so display text keeps clear character shapes across fonts and viewport sizes.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tiny-text`,id:`no-tiny-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tiny-text`,title:`Text is too small`,severity:`warn`,recommendation:`Use at least 12px for body text, and 16px is best. Small text is hard to read, especially on phones.`,category:`Accessibility`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-tiny-uppercase-tracked-label`,id:`no-tiny-uppercase-tracked-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-tiny-uppercase-tracked-label`,title:`Tiny label combines uppercase text and decorative tracking`,severity:`warn`,recommendation:`Use ordinary interface casing at a readable size instead of shrinking and spacing out short labels.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`,`react-jsx-only`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-transition-all`,id:`no-transition-all`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-transition-all`,title:`transition: all animates everything`,severity:`warn`,recommendation:'List the specific properties: `transition: "opacity 200ms, transform 200ms"`. In Tailwind, use `transition-colors`, `transition-opacity`, or `transition-transform`',category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-transitioned-composite-widget-state`,id:`no-transitioned-composite-widget-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-transitioned-composite-widget-state`,title:`Composite widget state feedback is delayed`,severity:`warn`,recommendation:`Keep selected, checked, and current-item color feedback instant in options, menus, and trees. Reserve transitions for the widget container opening or closing.`,category:`Accessibility`,framework:`global`,requires:[`tailwind`],tags:[`design`,`react-jsx-only`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-transitioned-focus-ring`,id:`no-transitioned-focus-ring`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-transitioned-focus-ring`,title:`Focus indicator animates into view`,severity:`warn`,recommendation:`Keep keyboard focus indicators instant. Restrict transitions to hover colors or transforms that do not delay the focus ring.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unbounded-animation-frame-loop`,id:`no-unbounded-animation-frame-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unbounded-animation-frame-loop`,title:`Animation frame loop has no stop path`,severity:`warn`,recommendation:`Add an explicit stop condition and retain each request ID so the active frame can be cancelled when the animation or component stops.`,category:`Performance`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-uncontrolled-input`,id:`no-uncontrolled-input`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uncontrolled-input`,title:`Uncontrolled input value`,severity:`warn`,recommendation:'Give `useState` a starting value (e.g. `useState("")` instead of `useState()`), add `onChange` (or `readOnly`) whenever you set `value`, and drop `defaultValue` on controlled inputs since React ignores it.',category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-undeferred-third-party`,id:`no-undeferred-third-party`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-undeferred-third-party`,title:`Render-blocking third-party script`,severity:`warn`,recommendation:'Use `next/script` with `strategy="lazyOnload"`, or add the `defer` attribute.',category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-undersized-icon-button`,id:`no-undersized-icon-button`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-undersized-icon-button`,title:`Icon button target is smaller than 24px`,severity:`warn`,recommendation:`Make the button at least 24 by 24 CSS pixels, or provide enough surrounding target spacing to satisfy the WCAG exception.`,category:`Accessibility`,framework:`global`,tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unescaped-dynamic-string-in-regexp`,id:`no-unescaped-dynamic-string-in-regexp`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unescaped-dynamic-string-in-regexp`,title:`Unescaped dynamic string in RegExp constructor`,severity:`warn`,recommendation:"A dynamic literal string such as a search term or path segment dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so values containing `.` or `(` over-match or throw. Escape the value with an `escapeRegExp` helper before constructing the pattern.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-unescaped-entities`,id:`no-unescaped-entities`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unescaped-entities`,title:`Unescaped entities in JSX`,severity:`warn`,recommendation:"Replace bare `'` / `\"` / `>` / `}` characters with HTML entities so literal UI text is encoded consistently.",category:`Bugs`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-ungated-tailwind-animation`,id:`no-ungated-tailwind-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-ungated-tailwind-animation`,title:`Tailwind animation ignores reduced motion`,severity:`warn`,recommendation:`Gate motion with motion-safe or provide a motion-reduce animation override that preserves the same information without spatial movement.`,category:`Accessibility`,framework:`global`,requires:[`react`,`tailwind`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-unguarded-browser-global-at-module-scope`,id:`no-unguarded-browser-global-at-module-scope`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-browser-global-at-module-scope`,title:`Browser global read at module scope`,severity:`warn`,recommendation:'Reading `window`/`navigator`/`localStorage` at module scope throws `ReferenceError: window is not defined` when the module is imported during SSR. Move the read inside a function/effect, or guard it with `typeof window !== "undefined"`.',category:`Bugs`,framework:`global`,requires:[`ssr`],isScanRule:!1}},{key:`react-doctor/no-unguarded-browser-global-in-render-or-hook-init`,id:`no-unguarded-browser-global-in-render-or-hook-init`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-browser-global-in-render-or-hook-init`,title:`Browser global read during server render`,severity:`error`,recommendation:`Move browser-only reads into an effect or event, guard them behind a client-only render path, or use useSyncExternalStore with a stable server snapshot.`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/no-unguarded-numeric-input-parse`,id:`no-unguarded-numeric-input-parse`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-numeric-input-parse`,title:`Unguarded numeric parse of an input value`,severity:`warn`,recommendation:"Guard `Number(e.target.value)` / `parseInt(e.target.value)` against empty and NaN before storing it. `Number('')` is `0` and `Number('abc')` is `NaN`, both of which silently ship a wrong value.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/no-unguarded-throwing-parse-call`,id:`no-unguarded-throwing-parse-call`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unguarded-throwing-parse-call`,title:`Unguarded call to a throwing parse API`,severity:`warn`,recommendation:"`decodeURIComponent`/`decodeURI`, color parsers (`readableColor`/`parseToRgb`/`chroma`), and single-arg `new URL(x)` on a URL/route value throw on malformed runtime input and crash render; guard with a validity pre-check (`URL.canParse`, `chroma.valid`), a try/catch, or a `safe*` helper that returns a fallback.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-uniform-feature-card-grid`,id:`no-uniform-feature-card-grid`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uniform-feature-card-grid`,title:`Feature grid repeats one card recipe`,severity:`warn`,recommendation:`Vary the composition, group related capabilities, or choose a denser list when every feature receives the same card weight.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-uninformative-aria-label`,id:`no-uninformative-aria-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uninformative-aria-label`,title:`Uninformative aria-label`,severity:`warn`,recommendation:'Name the action, not the element type: `aria-label="Search"`, not `aria-label="icon"` or `aria-label="button"`.',category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-unknown-property`,id:`no-unknown-property`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unknown-property`,title:`Unknown DOM property`,severity:`warn`,recommendation:"Use the prop name React expects, like `className`, `htmlFor`, or `tabIndex`, so the attribute is applied correctly.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-unowned-async-error-clear`,id:`no-unowned-async-error-clear`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unowned-async-error-clear`,title:`Stale async response clears newer request state`,severity:`warn`,recommendation:`Guard async completion by request identity, perform an ownership-aware functional state update, or key the state-owning component by request ID.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-unsafe`,id:`no-unsafe`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unsafe`,title:`Unsafe legacy lifecycle method`,severity:`warn`,recommendation:"Move setup to `constructor` or `componentDidMount`, prop-derived state to `getDerivedStateFromProps`, and update side effects to `componentDidUpdate` so React does not rely on deprecated unsafe lifecycles.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-unsafe-json-parse`,id:`no-unsafe-json-parse`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unsafe-json-parse`,title:`Unsafe JSON.parse dereference`,severity:`warn`,recommendation:"Wrap `JSON.parse(x)` in try/catch and validate the result (for example with a schema) before reading properties off it. A bare `JSON.parse(x).foo` throws on bad input and lets undefined fields slip past the type-checker.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-unstable-nested-components`,id:`no-unstable-nested-components`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-unstable-nested-components`,title:`Component defined inside a component`,severity:`warn`,recommendation:`Move nested components to module scope so React does not remount them and lose state on every render.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/no-unthrottled-scroll-mutation`,id:`no-unthrottled-scroll-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-unthrottled-scroll-mutation`,title:`Scroll handler drives animation every event`,severity:`warn`,recommendation:`Use a scroll or view timeline, IntersectionObserver, or a real timer throttle so animation work does not run for every scroll event.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-uppercase-mono-label`,id:`no-uppercase-mono-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uppercase-mono-label`,title:`Short label uses uppercase monospace styling`,severity:`warn`,recommendation:`Use the interface typeface and ordinary casing for labels; reserve monospace for values that are genuinely code-like.`,category:`Maintainability`,framework:`global`,requires:[`tailwind`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-uppercase-tracked-navigation-label`,id:`no-uppercase-tracked-navigation-label`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-uppercase-tracked-navigation-label`,title:`Navigation label uses uppercase tracking`,severity:`warn`,recommendation:`Use ordinary casing and tracking for persistent navigation labels.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-usememo-simple-expression`,id:`no-usememo-simple-expression`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-usememo-simple-expression`,title:`useMemo on a cheap value`,severity:`warn`,recommendation:`Remove the useMemo. Property reads, math, and ternaries are already fast, so wrapping them doesn't help`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-whole-object-default-losing-per-key-defaults`,id:`no-whole-object-default-losing-per-key-defaults`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-whole-object-default-losing-per-key-defaults`,title:`Whole-object param default loses per-key defaults`,severity:`warn`,recommendation:"A whole-object parameter default applies only when the argument is omitted entirely, so a partial argument makes every omitted key undefined. Move each fallback onto its own binding instead: `({ a = 1, b = false } = {})`.",category:`Bugs`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-whole-object-dep-with-member-reads`,id:`no-whole-object-dep-with-member-reads`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-whole-object-dep-with-member-reads`,title:`Whole props object in deps while only members are read`,severity:`warn`,recommendation:`Destructure the fields you read and depend on those bindings instead of the whole props object.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/no-wide-letter-spacing`,id:`no-wide-letter-spacing`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-wide-letter-spacing`,title:`Wide letter spacing on body text`,severity:`warn`,recommendation:`Save wide letter-spacing (over 0.05em) for short uppercase labels, nav items, and buttons, not body text.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/no-will-update-set-state`,id:`no-will-update-set-state`,source:`react-doctor`,originallyExternal:!0,rule:{id:`no-will-update-set-state`,title:`setState in componentWillUpdate`,severity:`warn`,recommendation:`Avoid setState in componentWillUpdate because it can loop forever; derive state before render or move guarded updates to componentDidUpdate.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/no-z-index-9999`,id:`no-z-index-9999`,source:`react-doctor`,originallyExternal:!1,rule:{id:`no-z-index-9999`,title:`Excessively high z-index`,severity:`warn`,recommendation:"Pick a small z-index scale, like dropdown 10, modal 20, toast 30. To layer something on top, use `isolation: isolate` instead of bigger numbers.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/nosql-injection-risk`,id:`nosql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`nosql-injection-risk`,title:`NoSQL query accepts operator-shaped input`,severity:`warn`,recommendation:"Coerce scalar fields before querying, reject operator keys from client input, and avoid `$where` or request-derived regexes.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/only-export-components`,id:`only-export-components`,source:`react-doctor`,originallyExternal:!0,rule:{id:`only-export-components`,title:`Non-component export in component file`,severity:`warn`,recommendation:`Move non-component exports out of component files so Fast Refresh can preserve component state instead of full-reloading.`,category:`Maintainability`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/package-metadata-secret`,id:`package-metadata-secret`,source:`react-doctor`,originallyExternal:!1,rule:{id:`package-metadata-secret`,title:`Secret-like package metadata`,severity:`warn`,recommendation:`Keep secrets out of package metadata and generated reports; they are often published to registries, logs, or browser artifacts.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/path-traversal-risk`,id:`path-traversal-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`path-traversal-risk`,title:`Filesystem path uses caller input`,severity:`warn`,recommendation:`Resolve paths against a fixed base directory, reject traversal after normalization, and map user-visible identifiers to server-owned paths.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/plugin-update-trust-risk`,id:`plugin-update-trust-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`plugin-update-trust-risk`,title:`Plugin or updater trust boundary risk`,severity:`warn`,recommendation:`Require signed updates/plugins, pin trusted repositories, verify hashes before execution, and keep custom repository installs behind explicit warnings.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/pointer-capture-needs-cancel-handler`,id:`pointer-capture-needs-cancel-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`pointer-capture-needs-cancel-handler`,title:`Captured pointer interaction has no cancellation path`,severity:`warn`,recommendation:"Handle `onPointerCancel` or `onLostPointerCapture` with the same cleanup used for pointer-up so interrupted drags cannot stay active.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/postmessage-origin-risk`,id:`postmessage-origin-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`postmessage-origin-risk`,title:`postMessage handler without origin check`,severity:`warn`,recommendation:"Validate `event.origin` against an exact allowlist before using `event.data`, especially when an iframe or parent window can be attacker-controlled.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/preact-no-children-length`,id:`preact-no-children-length`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-no-children-length`,title:`Array methods on Preact children can crash`,severity:`warn`,recommendation:"Wrap with `toChildArray(children)` because Preact's `props.children` is not always an array and array methods can crash.",category:`Bugs`,framework:`preact`,requires:[`preact`],isScanRule:!1}},{key:`react-doctor/preact-no-react-hooks-import`,id:`preact-no-react-hooks-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-no-react-hooks-import`,title:`React hook imports break pure Preact hook state`,severity:`warn`,recommendation:"Import hooks from `preact/hooks` so they share Preact's renderer state instead of loading a second hook implementation.",category:`Bugs`,framework:`preact`,requires:[`pure-preact`],isScanRule:!1}},{key:`react-doctor/preact-no-render-arguments`,id:`preact-no-render-arguments`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-no-render-arguments`,title:`render() reads props from arguments`,severity:`warn`,recommendation:"Read from `this.props` and `this.state` because `preact/compat` uses React's parameterless `render()` and positional props/state become undefined.",category:`Bugs`,framework:`preact`,requires:[`preact`],isScanRule:!1}},{key:`react-doctor/preact-prefer-ondblclick`,id:`preact-prefer-ondblclick`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-prefer-ondblclick`,title:`onDoubleClick instead of onDblClick`,severity:`warn`,recommendation:"Rename `onDoubleClick` to `onDblClick` because Preact core listens for the DOM `dblclick` event name and `onDoubleClick` never fires.",category:`Bugs`,framework:`preact`,requires:[`pure-preact`],isScanRule:!1}},{key:`react-doctor/preact-prefer-oninput`,id:`preact-prefer-oninput`,source:`react-doctor`,originallyExternal:!1,rule:{id:`preact-prefer-oninput`,title:`onChange instead of onInput`,severity:`warn`,recommendation:"Replace `onChange` with `onInput` on text-like inputs, or use `preact/compat` which remaps `onChange` automatically.",category:`Bugs`,framework:`preact`,requires:[`pure-preact`],isScanRule:!1}},{key:`react-doctor/prefer-dvh-over-vh`,id:`prefer-dvh-over-vh`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-dvh-over-vh`,title:`Use dvh instead of vh for full height`,severity:`warn`,recommendation:"Prefer `dvh` over `vh` for full-height elements. `100vh` overflows under mobile browser chrome; `100dvh` tracks the visible viewport. (`h-dvh`/`min-h-dvh` need Tailwind 3.4+.)",category:`Maintainability`,framework:`global`,requires:[`tailwind:3.4`],tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-dynamic-import`,id:`prefer-dynamic-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-dynamic-import`,title:`Heavy library loaded eagerly`,severity:`warn`,recommendation:"Load it only when needed: `const Component = dynamic(() => import('library'), { ssr: false })` from next/dynamic, or React.lazy().",category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-es6-class`,id:`prefer-es6-class`,source:`react-doctor`,originallyExternal:!0,rule:{id:`prefer-es6-class`,title:`createClass instead of ES6 class`,severity:`warn`,recommendation:"Pick one component style so readers do not have to switch between legacy `createReactClass` patterns and modern class components.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-explicit-variants`,id:`prefer-explicit-variants`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-explicit-variants`,title:`Prefer explicit variant components`,severity:`warn`,recommendation:"Replace boolean props that switch whole subtrees with explicit variant components, like `<ThreadComposer />` and `<EditMessageComposer />`, so each variant renders one clear path.",category:`Maintainability`,framework:`global`,tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/prefer-function-component`,id:`prefer-function-component`,source:`react-doctor`,originallyExternal:!0,rule:{id:`prefer-function-component`,title:`Class component instead of function`,severity:`warn`,recommendation:`Rewrite the class component as a function component so state and effects use modern hook patterns instead of class lifecycles.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-html-dialog`,id:`prefer-html-dialog`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-html-dialog`,title:`Custom modal instead of dialog`,severity:`warn`,recommendation:'Replace the wrapper with `<dialog>` and open it with `dialog.showModal()`. For the trigger, prefer `<button commandfor="id" command="show-modal">` (Chrome 135+), or a `useRef` with `dialogRef.current?.showModal()`.',category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/prefer-module-scope-pure-function`,id:`prefer-module-scope-pure-function`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-module-scope-pure-function`,title:`Pure function rebuilt every render`,severity:`warn`,recommendation:`Move the function above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted work.`,category:`Maintainability`,framework:`global`,disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-module-scope-static-value`,id:`prefer-module-scope-static-value`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-module-scope-static-value`,title:`Static value rebuilt every render`,severity:`warn`,recommendation:`Move the value above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted and makes it look new every time.`,category:`Maintainability`,framework:`global`,disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-motion-transform-property`,id:`prefer-motion-transform-property`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-motion-transform-property`,title:`Motion individual transforms miss compositor acceleration`,severity:`warn`,recommendation:"For animation that must remain smooth while JavaScript is busy, animate one `transform` string instead of Motion's individual transform keys.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`design`,`opt-in`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-stable-empty-fallback`,id:`prefer-stable-empty-fallback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-stable-empty-fallback`,title:`Empty fallback rebuilt each render`,severity:`warn`,recommendation:"Make a `const EMPTY = []` (or `{}`) at module scope and use it as the `||` / `??` fallback, so the value stays the same each render.",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`react-jsx-only`,`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-tabular-numeric-data`,id:`prefer-tabular-numeric-data`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-tabular-numeric-data`,title:`Changing table numbers use proportional figures`,severity:`warn`,recommendation:"Apply `tabular-nums` to dynamic numeric columns so digits keep a stable width and align while values change.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-tag-over-role`,id:`prefer-tag-over-role`,source:`react-doctor`,originallyExternal:!0,rule:{id:`prefer-tag-over-role`,title:`Role used instead of HTML tag`,severity:`warn`,recommendation:`Use the matching HTML element when one exists so browsers and assistive tech get native semantics.`,category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/prefer-truncate-shorthand`,id:`prefer-truncate-shorthand`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-truncate-shorthand`,title:`Use truncate shorthand`,severity:`warn`,recommendation:"Replace `overflow-hidden text-ellipsis whitespace-nowrap` with the single Tailwind `truncate` utility, which sets all three.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/prefer-use-effect-event`,id:`prefer-use-effect-event`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-use-effect-event`,title:`Effect re-subscribes on a changing callback`,severity:`warn`,recommendation:"Wrap the callback with `useEffectEvent(callback)` (React 19.2+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent",category:`Bugs`,framework:`global`,requires:[`react`,`react:19.2`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-use-sync-external-store`,id:`prefer-use-sync-external-store`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-use-sync-external-store`,title:`Hand-rolled external store subscription`,severity:`warn`,recommendation:"Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook gets this right during concurrent rendering and on the server; the hand-rolled version doesn't.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/prefer-useReducer`,id:`prefer-useReducer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`prefer-useReducer`,title:`Many related useState calls`,severity:`warn`,recommendation:"Group state that always changes together into `useReducer` so one dispatched action describes the whole transition instead of a list of setter calls.",category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/public-debug-artifact`,id:`public-debug-artifact`,source:`react-doctor`,originallyExternal:!1,rule:{id:`public-debug-artifact`,title:`Public debug artifact`,severity:`warn`,recommendation:`Remove debug artifacts from public output; logs and dumps often reveal source paths, internal routes, tokens, or environment snapshots.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/public-env-secret-name`,id:`public-env-secret-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`public-env-secret-name`,title:`Secret-like public env variable`,severity:`warn`,recommendation:`Public env prefixes are inlined into browser bundles. Rename public values to non-secret names, and keep tokens, passwords, private keys, and service-role credentials server-only.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/query-destructure-result`,id:`query-destructure-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-destructure-result`,title:`Spreading a whole query result subscribes to every field`,severity:`warn`,recommendation:"TanStack Query only subscribes to the fields you actually read, so `query.data` is as targeted as destructuring. Spreading the whole result reads every field and re-renders on each change; pick out only the fields you need.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-floating-mutate-async`,id:`query-floating-mutate-async`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-floating-mutate-async`,title:`Floating mutateAsync rejection`,severity:`warn`,recommendation:"Await, return, or handle rejection from the `mutateAsync()` promise so a failed mutation cannot become an unhandled rejection.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-mutation-missing-invalidation`,id:`query-mutation-missing-invalidation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-mutation-missing-invalidation`,title:`Mutation without cache invalidation`,severity:`warn`,recommendation:"Add `onSuccess: () => queryClient.invalidateQueries({ queryKey: ['...'] })` so cached data stays in sync after the mutation",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-mutation-in-effect-as-read`,id:`query-no-mutation-in-effect-as-read`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-mutation-in-effect-as-read`,title:`Mutation driven from an effect as a read`,severity:`warn`,recommendation:"Use `useQuery` with a `queryKey` and `enabled` for reads started by an effect so the result is cached and deduplicated.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-query-in-effect`,id:`query-no-query-in-effect`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-query-in-effect`,title:`Query refetch inside useEffect`,severity:`warn`,recommendation:"Use `queryKey` changes or `enabled` so React Query schedules the fetch once instead of refetching again from `useEffect`.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-rest-destructuring`,id:`query-no-rest-destructuring`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-rest-destructuring`,title:`Rest destructuring on query result`,severity:`warn`,recommendation:"Destructure only the fields you need, like `const { data, isLoading } = useQuery(...)`. Rest destructuring subscribes to every field and adds re-renders.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-usequery-for-mutation`,id:`query-no-usequery-for-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-usequery-for-mutation`,title:`useQuery used for mutation`,severity:`warn`,recommendation:"Use `useMutation()` for POST/PUT/DELETE. It gives onSuccess/onError callbacks, doesn't auto-refetch, and models writes correctly.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-no-void-query-fn`,id:`query-no-void-query-fn`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-no-void-query-fn`,title:`queryFn returns no value`,severity:`warn`,recommendation:"queryFn must return a value for the cache. Use the `enabled` option to conditionally disable the query instead of returning undefined",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/query-stable-query-client`,id:`query-stable-query-client`,source:`react-doctor`,originallyExternal:!1,rule:{id:`query-stable-query-client`,title:`Unstable QueryClient in component`,severity:`warn`,recommendation:"Move `new QueryClient()` to module scope, or wrap it in `useState(() => new QueryClient())`. Recreating it each render wipes the cache.",category:`Bugs`,framework:`tanstack-query`,requires:[`tanstack-query`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/r3f-cap-device-pixel-ratio`,id:`r3f-cap-device-pixel-ratio`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-cap-device-pixel-ratio`,title:`Unbounded device pixel ratio`,severity:`warn`,recommendation:`Cap devicePixelRatio, commonly at 2, or pass an explicit DPR range so high-density displays do not multiply rendering work without a bound`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-limit-shadowed-point-lights`,id:`r3f-limit-shadowed-point-lights`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-limit-shadowed-point-lights`,title:`Too many shadow-casting point lights`,severity:`warn`,recommendation:`Keep at most two shadow-casting point lights in one scene, or replace them with cheaper directional, spot, baked, or fake shadows`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-advancing-clock-in-use-frame`,id:`r3f-no-advancing-clock-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-advancing-clock-in-use-frame`,title:`Clock advanced inside useFrame`,severity:`warn`,recommendation:`Use the delta argument supplied to useFrame or read clock.elapsedTime without advancing the shared clock`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],disabledWhen:[`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-allocation-in-pointer-move`,id:`r3f-no-allocation-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-allocation-in-pointer-move`,title:`Allocation inside an R3F pointer-move handler`,severity:`warn`,recommendation:`Reuse component-owned vectors and Three.js objects while handling pointer movement`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-async-use-frame`,id:`r3f-no-async-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-async-use-frame`,title:`Async useFrame callback`,severity:`warn`,recommendation:`Keep useFrame synchronous; start asynchronous work outside the render loop and consume its completed state from the callback`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-clone-in-use-frame`,id:`r3f-no-clone-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-clone-in-use-frame`,title:`Three.js clone inside useFrame`,severity:`warn`,recommendation:`Clone once outside the frame loop or reuse a scratch vector, quaternion, matrix, or object allocated with useMemo or useRef`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-deep-use-three-selector`,id:`r3f-no-deep-use-three-selector`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-deep-use-three-selector`,title:`useThree selector reads a mutable Three.js field`,severity:`warn`,recommendation:`Select the stable R3F store object, then read its mutable Three.js fields where they are consumed`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`,`r3f:6`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-dispose-loader-cache`,id:`r3f-no-dispose-loader-cache`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-dispose-loader-cache`,title:`Disposal of a cached R3F loader asset`,severity:`warn`,recommendation:`Do not dispose assets returned by cached R3F and Drei loader hooks`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-duplicate-primitive-object`,id:`r3f-no-duplicate-primitive-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-duplicate-primitive-object`,title:`Primitive object mounted twice`,severity:`warn`,recommendation:`Mount a Three.js object through one <primitive>, or clone it deliberately when two independent instances are required`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-extend-in-render`,id:`r3f-no-extend-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-extend-in-render`,title:`R3F catalogue extension during render`,severity:`warn`,recommendation:`Call extend at module scope so React renders and Strict Mode replays do not repeatedly mutate R3F's global catalogue`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-extend-three-namespace`,id:`r3f-no-extend-three-namespace`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-extend-three-namespace`,title:`Whole Three.js namespace registered with R3F`,severity:`warn`,recommendation:`Pass extend an object containing only the Three.js constructors used by JSX so bundlers can tree-shake the rest of the namespace`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-fresh-portal-container`,id:`r3f-no-fresh-portal-container`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-fresh-portal-container`,title:`Fresh R3F portal container`,severity:`warn`,recommendation:`Keep the createPortal container stable with module scope, lazy state, or useMemo so R3F can preserve the portal store and event layer`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-fresh-use-three-selector`,id:`r3f-no-fresh-use-three-selector`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-fresh-use-three-selector`,title:`Fresh useThree selector result`,severity:`warn`,recommendation:`Select one stable store field at a time, or provide an equality function when returning an object or array from useThree`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`,`r3f:6`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-imperative-attach-of-managed-ref`,id:`r3f-no-imperative-attach-of-managed-ref`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-imperative-attach-of-managed-ref`,title:`Imperative attachment of an R3F-managed ref`,severity:`error`,recommendation:`Let R3F attach objects referenced by JSX. Do not add or attach the same ref.current imperatively`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-inline-primitive-object`,id:`r3f-no-inline-primitive-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-inline-primitive-object`,title:`Inline primitive object`,severity:`warn`,recommendation:`Create or clone the Three.js object once outside render, or memoize it, before passing it to <primitive>`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-inline-resource-prop`,id:`r3f-no-inline-resource-prop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-inline-resource-prop`,title:`Inline R3F resource prop`,severity:`warn`,recommendation:`Create the Three.js geometry or material once at module scope, in lazy state, or with useMemo before passing it as an R3F resource prop`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-internal-imports`,id:`r3f-no-internal-imports`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-internal-imports`,title:`Private R3F import`,severity:`warn`,recommendation:`Import from @react-three/fiber or one of its documented public entry points instead of a dist or src implementation path`,category:`Maintainability`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-manual-canvas-resize`,id:`r3f-no-manual-canvas-resize`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-manual-canvas-resize`,title:`Manual resize loop for an R3F-owned renderer`,severity:`warn`,recommendation:`Let Canvas and its ResizeObserver own renderer sizing instead of registering a window resize loop`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-mutate-loader-cache`,id:`r3f-no-mutate-loader-cache`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-mutate-loader-cache`,title:`Mutation of a cached R3F loader asset`,severity:`warn`,recommendation:`Clone the loaded object or resource before mutating it`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-mutating-pointer-event-data`,id:`r3f-no-mutating-pointer-event-data`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-mutating-pointer-event-data`,title:`Mutation of shared R3F pointer-event data`,severity:`warn`,recommendation:`Copy shared pointer-event vectors before applying local-space transforms or other mutations`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-new-in-use-frame`,id:`r3f-no-new-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-new-in-use-frame`,title:`Allocation inside useFrame`,severity:`warn`,recommendation:`Allocate Three.js objects once in module scope, useMemo, or useRef and mutate the reusable instance inside useFrame`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-null-loader-input`,id:`r3f-no-null-loader-input`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-null-loader-input`,title:`Nullish R3F loader input`,severity:`warn`,recommendation:`Conditionally render a child component that calls the loader hook only after it has a real asset URL`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-object-pointer-capture`,id:`r3f-no-object-pointer-capture`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-object-pointer-capture`,title:`Pointer capture called on an R3F scene object`,severity:`error`,recommendation:`Call pointer-capture methods on the R3F event target or currentTarget, not object or eventObject`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-recursive-raf-with-use-frame`,id:`r3f-no-recursive-raf-with-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-recursive-raf-with-use-frame`,title:`Competing animation loop alongside useFrame`,severity:`warn`,recommendation:`Use R3F's useFrame subscription as the component's single animation loop instead of starting a second browser or renderer loop`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-state-in-pointer-move`,id:`r3f-no-state-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-state-in-pointer-move`,title:`React state update inside an R3F pointer-move handler`,severity:`warn`,recommendation:`Keep pointer-move previews in Three.js refs or transient state and publish one semantic React update when the interaction commits`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-state-in-use-frame`,id:`r3f-no-state-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-state-in-use-frame`,title:`React state update inside useFrame`,severity:`warn`,recommendation:`Mutate Three.js refs or an external transient store inside useFrame; reserve React state for guarded, infrequent transitions`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-sync-readback-in-use-frame`,id:`r3f-no-sync-readback-in-use-frame`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-sync-readback-in-use-frame`,title:`Synchronous readback inside useFrame`,severity:`warn`,recommendation:`Move readback to a discrete or serialized async path and reuse the latest completed result during frames`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-no-unstable-args`,id:`r3f-no-unstable-args`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-unstable-args`,title:`Unstable R3F constructor args`,severity:`warn`,recommendation:`Keep reference-valued constructor arguments stable with module scope or useMemo so React Three Fiber does not reconstruct and dispose the Three.js object on a later render`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-no-use-frame-dependency-array`,id:`r3f-no-use-frame-dependency-array`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-no-use-frame-dependency-array`,title:`Dependency array passed to useFrame`,severity:`warn`,recommendation:`Remove the dependency array. Pass a numeric render priority on R3F v9, or supported scheduling options on R3F v10`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:3`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-prefer-instanced-mesh`,id:`r3f-prefer-instanced-mesh`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-prefer-instanced-mesh`,title:`Repeated R3F meshes use separate draw calls`,severity:`warn`,recommendation:`Render repeated meshes that share geometry and material with <instancedMesh> so they can use one draw call`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-prefer-use-loader`,id:`r3f-prefer-use-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-prefer-use-loader`,title:`Imperative Three.js loader in a React effect`,severity:`warn`,recommendation:`Load render resources with R3F useLoader so Suspense caching can share and deduplicate them`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`,`r3f:3`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-frame-delta`,id:`r3f-require-frame-delta`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-frame-delta`,title:`Frame-rate-dependent animation`,severity:`warn`,recommendation:`Scale incremental transforms and interpolation by useFrame delta, use delta-aware damping, or assign from absolute time`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-global-effect-cleanup`,id:`r3f-require-global-effect-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-global-effect-cleanup`,title:`Unreleased global R3F render-loop effect`,severity:`warn`,recommendation:`Register addEffect, addAfterEffect, and addTail from an effect and return the exact disposer so remounts do not retain global render-loop callbacks`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-instanced-buffer-update`,id:`r3f-require-instanced-buffer-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-instanced-buffer-update`,title:`Instanced mesh buffer is not marked for upload`,severity:`error`,recommendation:`After setMatrixAt or setColorAt, set the matching instance buffer's needsUpdate flag to true`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-owned-texture-cleanup`,id:`r3f-require-owned-texture-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-owned-texture-cleanup`,title:`Locally owned Three.js texture is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed textures in a React effect cleanup`,category:`Performance`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-projection-matrix-update`,id:`r3f-require-projection-matrix-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-projection-matrix-update`,title:`Missing camera projection-matrix update`,severity:`error`,recommendation:`Call camera.updateProjectionMatrix() after imperatively changing projection properties so Three.js renders the new frustum`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-render-with-positive-priority`,id:`r3f-require-render-with-positive-priority`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-render-with-positive-priority`,title:`Positive useFrame priority without a render`,severity:`warn`,recommendation:`Render the scene or composer from the positive-priority useFrame subscription, or use a non-positive priority to keep automatic rendering enabled`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-require-root-unmount`,id:`r3f-require-root-unmount`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-require-root-unmount`,title:`R3F root without unmount cleanup`,severity:`warn`,recommendation:`Unmount component-owned R3F roots in React cleanup, or return an exact disposer that unmounts the root`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-canvas-prop-compatibility`,id:`r3f-webgpu-canvas-prop-compatibility`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-canvas-prop-compatibility`,title:`Incompatible R3F Canvas renderer prop`,severity:`error`,recommendation:`Use renderer with @react-three/fiber/webgpu, gl with /legacy, and never pass both renderer APIs to one Canvas`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-gl-state`,id:`r3f-webgpu-no-gl-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-gl-state`,title:`WebGPU root reads deprecated gl state`,severity:`warn`,recommendation:`Read state.renderer from the R3F WebGPU root instead of state.gl`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-js-uniform-branch`,id:`r3f-webgpu-no-js-uniform-branch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-js-uniform-branch`,title:`JavaScript branch reads a TSL uniform value`,severity:`warn`,recommendation:`Express uniform-dependent control flow with TSL If, select, or Loop nodes so it runs on the GPU`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-legacy-effect-composer`,id:`r3f-webgpu-no-legacy-effect-composer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-legacy-effect-composer`,title:`Legacy EffectComposer inside an R3F WebGPU Canvas`,severity:`error`,recommendation:`Use the renderer's node-based post-processing pipeline instead of @react-three/postprocessing EffectComposer`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-legacy-material-api`,id:`r3f-webgpu-no-legacy-material-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-legacy-material-api`,title:`Legacy shader material API inside an R3F WebGPU Canvas`,severity:`error`,recommendation:`Use Three.js node materials and TSL for custom shaders rendered by the WebGPU backend`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`],tags:[`r3f`,`webgl`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/r3f-webgpu-no-unregistered-pipeline-pass`,id:`r3f-webgpu-no-unregistered-pipeline-pass`,source:`react-doctor`,originallyExternal:!1,rule:{id:`r3f-webgpu-no-unregistered-pipeline-pass`,title:`Unregistered WebGPU pipeline pass`,severity:`error`,recommendation:`Return custom passes from the WebGPU post-processing callback so R3F registers them in state.passes`,category:`Bugs`,framework:`global`,requires:[`react`,`r3f`,`r3f:10`],tags:[`r3f`,`webgl`],isScanRule:!1}},{key:`react-doctor/radio-input-missing-name`,id:`radio-input-missing-name`,source:`react-doctor`,originallyExternal:!1,rule:{id:`radio-input-missing-name`,title:`Radio input missing name`,severity:`warn`,recommendation:'Give every radio in the same group the same `name` prop (e.g. `<input type="radio" name="shippingSpeed" />`). The browser groups radios and enables arrow-key navigation only when they share a `name`.',category:`Accessibility`,framework:`global`,isScanRule:!1}},{key:`react-doctor/raw-sql-injection-risk`,id:`raw-sql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`raw-sql-injection-risk`,title:`Raw SQL built outside parameter binding`,severity:`warn`,recommendation:`Keep user input in driver parameters or ORM bind variables. Avoid unsafe/raw SQL helpers and string interpolation for queries.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/react-compiler-no-manual-memoization`,id:`react-compiler-no-manual-memoization`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-compiler-no-manual-memoization`,title:`Manual memoization in compiler-managed code`,severity:`warn`,recommendation:"Profile compiler-managed code and remove `useMemo`, `useCallback`, or `memo` only when the manual cache no longer carries behavioral or performance intent.",category:`Maintainability`,framework:`global`,requires:[`react-compiler`],isScanRule:!1}},{key:`react-doctor/react-in-jsx-scope`,id:`react-in-jsx-scope`,source:`react-doctor`,originallyExternal:!0,rule:{id:`react-in-jsx-scope`,title:`React not in scope for JSX`,severity:`warn`,recommendation:"If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import `React` at the top of the file.",category:`Bugs`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/react-markdown-unsanitized-raw-html`,id:`react-markdown-unsanitized-raw-html`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-markdown-unsanitized-raw-html`,title:`Unsanitized raw HTML in React Markdown`,severity:`warn`,recommendation:"Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/react-router-csp-nonce-consistency`,id:`react-router-csp-nonce-consistency`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-csp-nonce-consistency`,title:`CSP nonce is not shared across server rendering`,severity:`error`,recommendation:`Pass the same request-scoped nonce to ServerRouter and the React server-rendering stream options.`,category:`Security`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-descendant-routes-require-splat`,id:`react-router-descendant-routes-require-splat`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-descendant-routes-require-splat`,title:`Parent path cannot match descendant routes`,severity:`error`,recommendation:`End the mounting route path with /* so its descendant Routes tree can match deeper URLs.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-guard-aborted-handle-error`,id:`react-router-guard-aborted-handle-error`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-guard-aborted-handle-error`,title:`Aborted requests are reported as errors`,severity:`warn`,recommendation:`Return early when request.signal.aborted before reporting the error from handleError.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-internal-route-anchor`,id:`react-router-internal-route-anchor`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-internal-route-anchor`,title:`Internal route uses a document navigation`,severity:`warn`,recommendation:`Use React Router Link for navigation to a known UI route.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-loader-fetch-forwards-signal`,id:`react-router-loader-fetch-forwards-signal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-loader-fetch-forwards-signal`,title:`Loader fetch ignores cancellation`,severity:`warn`,recommendation:`Pass request.signal to fetch so superseded navigations cancel work that no longer contributes to the route.`,category:`Performance`,framework:`global`,requires:[`react`,`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-loader-parallel-fetch`,id:`react-router-loader-parallel-fetch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-loader-parallel-fetch`,title:`Independent loader work runs sequentially`,severity:`warn`,recommendation:`Start independent loader work together with Promise.all to avoid adding serial latency to navigation.`,category:`Performance`,framework:`global`,requires:[`react`,`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-nested-route-requires-outlet`,id:`react-router-nested-route-requires-outlet`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-nested-route-requires-outlet`,title:`Nested routes have no Outlet`,severity:`error`,recommendation:`Render <Outlet /> from the parent route component where child routes belong.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-catch-middleware-next`,id:`react-router-no-catch-middleware-next`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-catch-middleware-next`,title:`Middleware catch cannot observe downstream errors`,severity:`warn`,recommendation:`Use an ErrorBoundary, handleError, or middleware response instrumentation instead of catching next().`,category:`Bugs`,framework:`global`,requires:[`react-router:7.8`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-client-module-in-server-render`,id:`react-router-no-client-module-in-server-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-client-module-in-server-render`,title:`Client-only module rendered on the server`,severity:`error`,recommendation:`Move client-only behavior behind a hydration boundary instead of importing a .client module into server-rendered JSX.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`,`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-no-duplicate-route-id`,id:`react-router-no-duplicate-route-id`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-duplicate-route-id`,title:`Duplicate route ID`,severity:`error`,recommendation:`Give every explicit route ID a unique value within the router.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-empty-leaf-route`,id:`react-router-no-empty-leaf-route`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-empty-leaf-route`,title:`Leaf route renders nothing`,severity:`warn`,recommendation:`Add element, Component, or lazy to a UI leaf route, or remove the empty route.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-invalid-absolute-child-path`,id:`react-router-no-invalid-absolute-child-path`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-invalid-absolute-child-path`,title:`Absolute child path escapes its parent`,severity:`error`,recommendation:`Make the child path relative, or prefix its absolute path with the complete parent route path.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-invalid-lazy-route-properties`,id:`react-router-no-invalid-lazy-route-properties`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-invalid-lazy-route-properties`,title:`Immutable route property returned from lazy`,severity:`error`,recommendation:`Keep route-matching properties on the static route object; lazy may return only non-matching route properties.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.9`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-invalid-splat-path`,id:`react-router-no-invalid-splat-path`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-invalid-splat-path`,title:`Invalid splat route path`,severity:`error`,recommendation:`Use '*' for a catch-all route or end a path with '/*'.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-loader-request-body`,id:`react-router-no-loader-request-body`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-loader-request-body`,title:`Loader reads a request body`,severity:`error`,recommendation:`Read submitted request bodies in an action; loaders handle GET requests and should use URL search parameters instead.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-middleware-response-body-consumption`,id:`react-router-no-middleware-response-body-consumption`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-middleware-response-body-consumption`,title:`Middleware consumes the outgoing response body`,severity:`warn`,recommendation:`Inspect or change response headers and status without consuming the body returned by next().`,category:`Bugs`,framework:`global`,requires:[`react-router:7.9`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-multiple-blockers`,id:`react-router-no-multiple-blockers`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-multiple-blockers`,title:`Multiple blockers in one component`,severity:`error`,recommendation:`Combine blocking conditions into one useBlocker call per rendered component.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.7`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-multiple-middleware-next`,id:`react-router-no-multiple-middleware-next`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-multiple-middleware-next`,title:`Middleware continuation called twice`,severity:`error`,recommendation:`Call next exactly once and reuse the returned Response.`,category:`Bugs`,framework:`global`,requires:[`react-router:7.9`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-multiple-set-search-params-in-tick`,id:`react-router-no-multiple-set-search-params-in-tick`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-multiple-set-search-params-in-tick`,title:`Search params updated multiple times`,severity:`warn`,recommendation:`Combine changes into one setSearchParams call because updates in the same tick do not queue like React state.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-navigate-in-render`,id:`react-router-no-navigate-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-navigate-in-render`,title:`navigate() called during render`,severity:`error`,recommendation:`Render <Navigate> for declarative redirects, redirect from a loader or action, or navigate from an event or effect.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-nested-router`,id:`react-router-no-nested-router`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-nested-router`,title:`Router nested inside another router`,severity:`error`,recommendation:`Keep one router provider at the application root.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-no-redirect-in-try-catch`,id:`react-router-no-redirect-in-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-redirect-in-try-catch`,title:`redirect() inside try-catch`,severity:`error`,recommendation:`Move redirect outside the guarded try block or rethrow redirect responses from catch.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-route-module-environment-suffix`,id:`react-router-no-route-module-environment-suffix`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-route-module-environment-suffix`,title:`Route module has an environment suffix`,severity:`error`,recommendation:`Keep the route module environment-neutral and move environment-specific code into imported .client or .server modules.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-no-router-in-render`,id:`react-router-no-router-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-router-in-render`,title:`Router created during render`,severity:`error`,recommendation:`Create the router at module scope so navigation state and subscriptions survive renders.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-session-mutation-in-loader`,id:`react-router-no-session-mutation-in-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-session-mutation-in-loader`,title:`Loader mutates session state`,severity:`error`,recommendation:`Mutate and commit sessions in an action, then redirect to a loader.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-static-cookie-expires`,id:`react-router-no-static-cookie-expires`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-static-cookie-expires`,title:`Cookie expiry is fixed at module load`,severity:`error`,recommendation:`Use maxAge for a duration-based cookie lifetime.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-unsynchronized-search-params-mutation`,id:`react-router-no-unsynchronized-search-params-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-unsynchronized-search-params-mutation`,title:`Search params mutated without navigation`,severity:`error`,recommendation:`Clone the URLSearchParams value, mutate the clone, and return or pass it to setSearchParams.`,category:`Bugs`,framework:`global`,requires:[`react-router`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-no-use-loader-data-in-error-ui`,id:`react-router-no-use-loader-data-in-error-ui`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-no-use-loader-data-in-error-ui`,title:`Error UI assumes loader data exists`,severity:`error`,recommendation:`Use useRouteLoaderData with an explicit route ID and handle undefined inside error UI.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-prefer-route-lazy`,id:`react-router-prefer-route-lazy`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-prefer-route-lazy`,title:`React.lazy creates a route-module waterfall`,severity:`warn`,recommendation:`Load route modules with the route lazy property so initial navigation does not download every route component.`,category:`Performance`,framework:`global`,requires:[`react`,`react-router:6.9`],disabledWhen:[`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-require-root-error-boundary`,id:`react-router-require-root-error-boundary`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-require-root-error-boundary`,title:`Root route lacks an error boundary`,severity:`error`,recommendation:`Define ErrorBoundary or errorElement on the root route so rendering, loader, and action failures have an application fallback.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-resource-link-requires-reload`,id:`react-router-resource-link-requires-reload`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-resource-link-requires-reload`,title:`Resource link intercepted as navigation`,severity:`error`,recommendation:`Add reloadDocument to resource links so the browser downloads or opens the resource instead of client-routing it.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/react-router-return-navigation-promise-in-transition`,id:`react-router-return-navigation-promise-in-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-return-navigation-promise-in-transition`,title:`Transition drops a navigation promise`,severity:`warn`,recommendation:`Return or await the navigation promise from the transition callback so pending state lasts through navigation.`,category:`Bugs`,framework:`global`,requires:[`react-router:7.10`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-server-middleware-return-response`,id:`react-router-server-middleware-return-response`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-server-middleware-return-response`,title:`Server middleware drops the Response`,severity:`error`,recommendation:`Return the Response produced by next(), or return an explicit replacement Response.`,category:`Bugs`,framework:`global`,requires:[`react-router:7.9`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-session-mutation-requires-commit`,id:`react-router-session-mutation-requires-commit`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-session-mutation-requires-commit`,title:`Session mutation is not committed`,severity:`error`,recommendation:`Serialize the session with commitSession or destroySession and include its Set-Cookie value in the returned Response.`,category:`Bugs`,framework:`global`,requires:[`react-router:7`,`react-router-framework`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/react-router-v8-no-meta-data-field`,id:`react-router-v8-no-meta-data-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-v8-no-meta-data-field`,title:`Removed meta data field`,severity:`error`,recommendation:`Read loaderData instead of the removed data field.`,category:`Maintainability`,framework:`global`,requires:[`react-router:8`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-v8-no-react-router-dom-import`,id:`react-router-v8-no-react-router-dom-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-v8-no-react-router-dom-import`,title:`Removed react-router-dom import`,severity:`error`,recommendation:`Import DOM-only APIs from react-router/dom and all other APIs from react-router.`,category:`Maintainability`,framework:`global`,requires:[`react-router:8`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-v8-no-removed-future-flags`,id:`react-router-v8-no-removed-future-flags`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-v8-no-removed-future-flags`,title:`Removed React Router future flag`,severity:`error`,recommendation:`Remove v8 future flags; move v8_splitRouteModules to the top-level splitRouteModules option.`,category:`Maintainability`,framework:`global`,requires:[`react-router:8`,`react-router-framework`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/react-router-valid-route-object`,id:`react-router-valid-route-object`,source:`react-doctor`,originallyExternal:!1,rule:{id:`react-router-valid-route-object`,title:`Contradictory route object`,severity:`error`,recommendation:`Remove incompatible route properties so each route has one unambiguous rendering contract.`,category:`Bugs`,framework:`global`,requires:[`react-router:6.4`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/redux-useselector-inline-derivation`,id:`redux-useselector-inline-derivation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`redux-useselector-inline-derivation`,title:`useSelector derives data inline`,severity:`warn`,recommendation:`Select the raw slice and memoize derivation so Redux actions do not rebuild a collection and redraw this component.`,category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/redux-useselector-returns-new-collection`,id:`redux-useselector-returns-new-collection`,source:`react-doctor`,originallyExternal:!1,rule:{id:`redux-useselector-returns-new-collection`,title:`useSelector returns a new collection`,severity:`warn`,recommendation:"Return a stable selected value, split selectors, or pass `shallowEqual` so every Redux action does not redraw this component.",category:`Performance`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/remotion-calculate-metadata-fetch-signal`,id:`remotion-calculate-metadata-fetch-signal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-calculate-metadata-fetch-signal`,title:`calculateMetadata fetch ignores abortSignal`,severity:`error`,recommendation:"Destructure `abortSignal` from the calculateMetadata argument and pass it to direct fetch calls as `{signal: abortSignal}`.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-deterministic-randomness`,id:`remotion-deterministic-randomness`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-deterministic-randomness`,title:`Randomness changes between rendered frames`,severity:`error`,recommendation:"Use Remotion's seeded `random(seed)` helper so the same frame produces the same value in every render tab.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-css-animation`,id:`remotion-no-css-animation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-css-animation`,title:`CSS animation is not frame-driven`,severity:`error`,recommendation:"Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-css-transition`,id:`remotion-no-css-transition`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-css-transition`,title:`CSS transition is not frame-driven`,severity:`error`,recommendation:"Drive the property from `useCurrentFrame()` with `interpolate()` so every rendered frame is deterministic.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-css-url-assets`,id:`remotion-no-css-url-assets`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-css-url-assets`,title:`CSS URL asset can flicker in Remotion`,severity:`error`,recommendation:"Render the asset with `Img` inside an `AbsoluteFill`, or preload the same source with a hidden `Img` when a CSS mask is required.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-module-scope-delay-render`,id:`remotion-no-module-scope-delay-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-module-scope-delay-render`,title:`Module-scoped delayRender blocks every composition`,severity:`error`,recommendation:"Create the handle once inside the component. Use `useDelayRender()` on Remotion 4.0.342 or newer, or lazy `useState(() => delayRender())` on earlier versions.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],isScanRule:!1}},{key:`react-doctor/remotion-no-native-media-elements`,id:`remotion-no-native-media-elements`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-native-media-elements`,title:`Native media element bypasses Remotion loading`,severity:`error`,recommendation:`Use Remotion's media components so rendering waits for assets and seeks media to the requested frame.`,category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-no-next-image`,id:`remotion-no-next-image`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-no-next-image`,title:`Next.js Image can flicker in Remotion`,severity:`error`,recommendation:"Use `Img` from `remotion`, which delays rendering until the image is loaded.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/remotion-stable-delay-render-handle`,id:`remotion-stable-delay-render-handle`,source:`react-doctor`,originallyExternal:!1,rule:{id:`remotion-stable-delay-render-handle`,title:`delayRender handle is recreated during render`,severity:`error`,recommendation:"Prefer `useDelayRender()`, or initialize `delayRender()` once with `useState(() => delayRender())`.",category:`Bugs`,framework:`global`,requires:[`remotion:4`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/rendering-animate-svg-wrapper`,id:`rendering-animate-svg-wrapper`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-animate-svg-wrapper`,title:`Animating an SVG directly`,severity:`warn`,recommendation:`Wrap the SVG in a motion element so animation props apply to a stable wrapper instead of the SVG node itself.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-conditional-render`,id:`rendering-conditional-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-conditional-render`,title:`Number before && renders stray 0`,severity:`warn`,recommendation:"Use `{items.length > 0 && <List />}`, or a ternary like `{items.length ? <List /> : null}`.",category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/rendering-hoist-jsx`,id:`rendering-hoist-jsx`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-hoist-jsx`,title:`Constant JSX rebuilt each render`,severity:`warn`,recommendation:"Move the static JSX out to the top of the file: `const ICON = <svg>...</svg>`, so it isn't rebuilt on every render",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-hydration-mismatch-time`,id:`rendering-hydration-mismatch-time`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-hydration-mismatch-time`,title:`Time or random value in JSX`,severity:`warn`,recommendation:`Move time or random values into useEffect+useState so they only run in the browser, or add suppressHydrationWarning to the parent if it's intentional`,category:`Bugs`,framework:`global`,requires:[`react`,`ssr`],isScanRule:!1}},{key:`react-doctor/rendering-hydration-no-flicker`,id:`rendering-hydration-no-flicker`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-hydration-no-flicker`,title:`useEffect setState flashes on mount`,severity:`warn`,recommendation:"Initialize state from a render-safe value before the first paint, or read external mutable values with `useSyncExternalStore`.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-script-defer-async`,id:`rendering-script-defer-async`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-script-defer-async`,title:`Script without defer or async`,severity:`warn`,recommendation:'Add `defer` for scripts that need the page, or `async` for standalone ones like analytics. In Next.js, use `<Script strategy="afterInteractive" />`',category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-svg-precision`,id:`rendering-svg-precision`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-svg-precision`,title:`Overly precise SVG path values`,severity:`warn`,recommendation:`Round path, points, and transform decimals to 1 or 2 digits. The extra precision adds bytes with no visible difference.`,category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rendering-usetransition-loading`,id:`rendering-usetransition-loading`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rendering-usetransition-loading`,title:`Loading useState forces extra render`,severity:`warn`,recommendation:"Replace with `const [isPending, startTransition] = useTransition()`, which marks the update as non-urgent and interruptible so the input stays responsive",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/repository-secret-file`,id:`repository-secret-file`,source:`react-doctor`,originallyExternal:!1,rule:{id:`repository-secret-file`,title:`Secret file checked into repository`,severity:`error`,recommendation:`Remove committed env files, service-account credentials, npm auth tokens, and webhook URLs; rotate exposed values and keep only redacted examples in source.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/request-body-mass-assignment`,id:`request-body-mass-assignment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`request-body-mass-assignment`,title:`Request input spread without field allowlist`,severity:`warn`,recommendation:"Assign explicit, allowlisted fields (or validate with a strict schema and no `.passthrough()`) instead of spreading/merging request input. Otherwise the client can set ownership, role, or price columns (mass assignment) or pollute the prototype.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/require-autoplay-video-poster`,id:`require-autoplay-video-poster`,source:`react-doctor`,originallyExternal:!1,rule:{id:`require-autoplay-video-poster`,title:`Autoplay video has no poster frame`,severity:`warn`,recommendation:"Provide a representative `poster` image so the video region is intentional before playback begins and while media is unavailable.",category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/require-render-return`,id:`require-render-return`,source:`react-doctor`,originallyExternal:!0,rule:{id:`require-render-return`,title:`Render method does not return`,severity:`error`,recommendation:"Return JSX or `null` from `render` so the component intentionally shows something or nothing.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/require-scale-reveal-transform-origin`,id:`require-scale-reveal-transform-origin`,source:`react-doctor`,originallyExternal:!1,rule:{id:`require-scale-reveal-transform-origin`,title:`Scaled menu omits its transform origin`,severity:`warn`,recommendation:`Anchor scaled menus and listboxes to the edge nearest their trigger.`,category:`Maintainability`,framework:`global`,tags:[`design`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/rerender-defer-reads-hook`,id:`rerender-defer-reads-hook`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-defer-reads-hook`,title:`URL hook value only read in handlers`,severity:`warn`,recommendation:"Read the URL inside the handler (e.g. `new URL(window.location.href).searchParams`) so the component doesn't redraw every time the URL changes.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-dependencies`,id:`rerender-dependencies`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-dependencies`,title:`Unstable value recreated every render`,severity:`error`,recommendation:`Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.`,category:`Bugs`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-derived-state-from-hook`,id:`rerender-derived-state-from-hook`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-derived-state-from-hook`,title:`Continuous hook value over-renders`,severity:`warn`,recommendation:'Use a threshold hook like `useMediaQuery("(max-width: 767px)")`, so the screen only redraws when the answer changes, not on every pixel',category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-functional-setstate`,id:`rerender-functional-setstate`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-functional-setstate`,title:`setState reads a stale value`,severity:`warn`,recommendation:"Use the callback form: `setState(prev => prev + 1)` to always read the latest value",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-lazy-ref-init`,id:`rerender-lazy-ref-init`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-lazy-ref-init`,title:`Ref initializer runs on every render`,severity:`warn`,recommendation:`Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-lazy-state-init`,id:`rerender-lazy-state-init`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-lazy-state-init`,title:`State initializer runs on every render`,severity:`warn`,recommendation:`Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-memo-before-early-return`,id:`rerender-memo-before-early-return`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-memo-before-early-return`,title:`useMemo before an early return`,severity:`warn`,recommendation:`Move the JSX into a child component rendered after the early return, so renders that take the early return never build it`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-memo-with-default-value`,id:`rerender-memo-with-default-value`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-memo-with-default-value`,title:`Empty default prop breaks memo`,severity:`warn`,recommendation:"Move it to the top of the file: `const EMPTY_ITEMS: Item[] = []`, then use that as the default value",category:`Performance`,framework:`global`,requires:[`react`],disabledWhen:[`react-compiler`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-state-only-in-handlers`,id:`rerender-state-only-in-handlers`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-state-only-in-handlers`,title:`State only used in handlers`,severity:`warn`,recommendation:"Use useRef instead of useState when the value is only set and never shown on screen. `ref.current = ...` updates it without redrawing the component.",category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rerender-transitions-scroll`,id:`rerender-transitions-scroll`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rerender-transitions-scroll`,title:`setState in a scroll handler`,severity:`warn`,recommendation:`Wrap the setState in startTransition, use useDeferredValue, or keep the value in a ref and throttle with requestAnimationFrame, so these events don't redraw the screen every time`,category:`Performance`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-animate-layout-property`,id:`rn-animate-layout-property`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-animate-layout-property`,title:`Animating a layout property`,severity:`warn`,recommendation:"Retired: Reanimated `useAnimatedStyle` can safely drive layout-affecting properties on the UI thread, so this pattern should not be flagged.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/rn-animation-reaction-as-derived`,id:`rn-animation-reaction-as-derived`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-animation-reaction-as-derived`,title:`useAnimatedReaction just copies a value`,severity:`warn`,recommendation:"This useAnimatedReaction just copies one value to another. Replace it with `useDerivedValue(() => ..., [deps])`, which is shorter and tracks changes for you.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-no-ignored-scroll-prop`,id:`rn-bottom-sheet-no-ignored-scroll-prop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-no-ignored-scroll-prop`,title:`Ignored BottomSheetScrollView prop`,severity:`warn`,recommendation:`Remove scrollEventThrottle, decelerationRate, and onScrollBeginDrag from BottomSheetScrollView because the component ignores them.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-no-state-in-on-animate`,id:`rn-bottom-sheet-no-state-in-on-animate`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-no-state-in-on-animate`,title:`React state update in Bottom Sheet onAnimate`,severity:`warn`,recommendation:`Avoid starting React renders from onAnimate. Use animatedIndex or animatedPosition for animation-coupled UI, or onChange for committed index state.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-prefer-native`,id:`rn-bottom-sheet-prefer-native`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-prefer-native`,title:`JS bottom sheet misses native sheet behavior`,severity:`warn`,recommendation:'When native presentation fits the design, use `<Modal presentationStyle="formSheet">` for platform-native gestures, accessibility, and presentation behavior.',category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-bottom-sheet-use-integrated-scrollable`,id:`rn-bottom-sheet-use-integrated-scrollable`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-bottom-sheet-use-integrated-scrollable`,title:`React Native scrollable inside a Bottom Sheet`,severity:`warn`,recommendation:`Use @gorhom/bottom-sheet's integrated BottomSheetScrollView, BottomSheetFlatList, BottomSheetSectionList, or BottomSheetVirtualizedList so gestures coordinate with the sheet.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-detox-missing-await`,id:`rn-detox-missing-await`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-detox-missing-await`,title:`Un-awaited Detox action`,severity:`warn`,recommendation:"Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-list-callback-per-row`,id:`rn-list-callback-per-row`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-callback-per-row`,title:`Inline handler in list renderItem`,severity:`warn`,recommendation:`Move the handler out with useCallback at list scope and pass the row id as a prop. Then memo() rows skip redrawing when their data has not changed.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-list-data-mapped`,id:`rn-list-data-mapped`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-data-mapped`,title:`List data rebuilt every render`,severity:`warn`,recommendation:"This builds a new array each time the parent redraws, so every row redraws too. Wrap it in `useMemo(() => items.map(...), [items])` to keep the same array.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-list-missing-estimated-item-size`,id:`rn-list-missing-estimated-item-size`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-missing-estimated-item-size`,title:`List missing estimatedItemSize`,severity:`warn`,recommendation:"Without `estimatedItemSize` the list guesses row height and can flash blank cells on fast scroll. Add `estimatedItemSize={<avg-row-height-in-px>}` so it matches your rows.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-list-recyclable-without-types`,id:`rn-list-recyclable-without-types`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-list-recyclable-without-types`,title:`Recyclable list missing getItemType`,severity:`warn`,recommendation:"When rows have different shapes, reused cells can show the wrong layout. Add `getItemType` that returns a stable type for each row shape so FlashList keeps separate recycling pools.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-deep-imports`,id:`rn-no-deep-imports`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-deep-imports`,title:`Deep import into react-native internals`,severity:`warn`,recommendation:"Import the symbol from `react-native` (the package root) instead of the deprecated `react-native/Libraries/...` subpath, which RFC 0894 removes on upgrade.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-deprecated-modules`,id:`rn-no-deprecated-modules`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-deprecated-modules`,title:`Module removed from react-native core`,severity:`error`,recommendation:`These modules were removed from react-native core. Import them from the community package instead.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-dimensions-get`,id:`rn-no-dimensions-get`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-dimensions-get`,title:`Dimensions.get over useWindowDimensions`,severity:`warn`,recommendation:"Use `const { width, height } = useWindowDimensions()` so the size updates automatically on rotation and resize.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-falsy-and-render`,id:`rn-no-falsy-and-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-falsy-and-render`,title:`Numeric && renders bare zero`,severity:`error`,recommendation:"When the number is 0, this shows a bare `0` as text, which crashes on RN. Use `{value > 0 && <X />}`, `{Boolean(value) && <X />}`, or `{value ? <X /> : null}`.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-image-children`,id:`rn-no-image-children`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-image-children`,title:`Children inside react-native <Image>`,severity:`error`,recommendation:"React Native's `<Image>` can't render children. Use `<ImageBackground>` (same `source`/`style` props) to layer content over an image.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-inline-flatlist-renderitem`,id:`rn-no-inline-flatlist-renderitem`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-inline-flatlist-renderitem`,title:`Inline renderItem on list`,severity:`warn`,recommendation:`Move renderItem to a named function or wrap it in useCallback so it is not rebuilt every time the screen redraws.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-inline-object-in-list-item`,id:`rn-no-inline-object-in-list-item`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-inline-object-in-list-item`,title:`Inline object in list renderItem`,severity:`warn`,recommendation:`Move style and object props out of renderItem (StyleSheet.create, useMemo at list scope, or pass primitives) so memo() rows stop redrawing when their data has not changed.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],disabledWhen:[`react-compiler`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-legacy-expo-packages`,id:`rn-no-legacy-expo-packages`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-legacy-expo-packages`,title:`Unmaintained legacy Expo package`,severity:`warn`,recommendation:`Switch to the maintained replacement package so users are not stuck with unfixed bugs in deprecated Expo packages.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-legacy-shadow-styles`,id:`rn-no-legacy-shadow-styles`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-legacy-shadow-styles`,title:`Legacy platform-specific shadow styles`,severity:`warn`,recommendation:"Use `boxShadow` for shadows that work on both platforms on the new architecture, instead of platform-specific shadow properties.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-non-native-navigator`,id:`rn-no-non-native-navigator`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-non-native-navigator`,title:`Non-native JS navigator`,severity:`warn`,recommendation:"Use `@react-navigation/native-stack` for stack navigation. Treat drawers separately because standalone React Navigation has no native drawer replacement.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-panresponder`,id:`rn-no-panresponder`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-panresponder`,title:`PanResponder over react-native-gesture-handler`,severity:`warn`,recommendation:"Use `react-native-gesture-handler` (`Gesture.Pan()`) instead of `PanResponder`. It runs gestures on the native UI thread, so they stay smooth even when the JS thread is busy.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-raw-text`,id:`rn-no-raw-text`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-raw-text`,title:`Raw text outside a Text component`,severity:`error`,recommendation:"Text outside a `<Text>` component crashes on React Native. Wrap it like `<Text>{value}</Text>`.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-renderitem-key`,id:`rn-no-renderitem-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-renderitem-key`,title:`renderItem key is ignored by React Native lists`,severity:`warn`,recommendation:"A `key` on the JSX from renderItem does nothing, since lists get row keys from `keyExtractor` (or `item.key`). Remove it and set `keyExtractor` on the list instead.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-scroll-state`,id:`rn-no-scroll-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-scroll-state`,title:`setState in onScroll handler`,severity:`error`,recommendation:"`setState` on every scroll event redraws the screen dozens of times a second. Track the position with a Reanimated shared value (`useAnimatedScrollHandler`) or a ref.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-scrollview-mapped-list`,id:`rn-no-scrollview-mapped-list`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-scrollview-mapped-list`,title:`Non-virtualized mapped list in ScrollView`,severity:`warn`,recommendation:"`<ScrollView>{items.map(...)}</ScrollView>` builds every row at once, which slows scrolling. Use FlashList, LegendList, or FlatList instead.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-no-set-native-props`,id:`rn-no-set-native-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-set-native-props`,title:`Imperative setNativeProps (no-op under Fabric)`,severity:`warn`,recommendation:"Drive the prop through React state, an `Animated.Value` (with `useNativeDriver: true`), or a Reanimated shared value. `setNativeProps` is a silent no-op under the New Architecture.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`],isScanRule:!1}},{key:`react-doctor/rn-no-single-element-style-array`,id:`rn-no-single-element-style-array`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-no-single-element-style-array`,title:`Single-element style array adds wasted allocation`,severity:`warn`,recommendation:"Use `style={value}` instead of `style={[value]}`. A one-item array just adds extra work for nothing.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-platform-shaking-use-direct-import`,id:`rn-platform-shaking-use-direct-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-platform-shaking-use-direct-import`,title:`Platform reached through React Native namespace`,severity:`warn`,recommendation:'Import `Platform` directly with `import { Platform } from "react-native"` so Expo can remove code for the other platform.',category:`Bugs`,framework:`react-native`,requires:[`expo:54`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-content-inset-adjustment`,id:`rn-prefer-content-inset-adjustment`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-content-inset-adjustment`,title:`Manual safe-area insets can duplicate offsets`,severity:`warn`,recommendation:`Retired: SafeAreaView wrappers are valid; prefer native content inset adjustment only when manual inset plumbing causes scroll jumps or duplicated safe-area offsets.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/rn-prefer-expo-image`,id:`rn-prefer-expo-image`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-expo-image`,title:`react-native Image instead of expo-image`,severity:`warn`,recommendation:"Use `<Image>` from `expo-image` instead of `react-native`. Same props, plus caching, placeholders, and crossfades for faster image loading.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-pressable`,id:`rn-prefer-pressable`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-pressable`,title:`Touchable components instead of Pressable`,severity:`warn`,recommendation:"Use `<Pressable>` because Touchable* components are frozen and lack Pressable's state-based feedback and accessibility behavior.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-pressable-over-gesture-detector`,id:`rn-prefer-pressable-over-gesture-detector`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-pressable-over-gesture-detector`,title:`GestureDetector for a plain tap`,severity:`warn`,recommendation:"Every GestureDetector sets up a native handler when it appears, which slows the screen. For a plain tap, use `<Pressable>` (or `createCSSAnimatedComponent(Pressable)` from react-native-reanimated/css for animated press feedback).",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-prefer-reanimated`,id:`rn-prefer-reanimated`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-prefer-reanimated`,title:`JS-thread animation instead of Reanimated`,severity:`warn`,recommendation:"Use `import Animated from 'react-native-reanimated'` so animations run on the UI thread instead of the JS thread, which keeps them smooth.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-pressable-shared-value-mutation`,id:`rn-pressable-shared-value-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-pressable-shared-value-mutation`,title:`Pressable animates on the JS thread`,severity:`warn`,recommendation:`This press animation runs on the JS thread, so it can stutter. Wrap it in <GestureDetector gesture={Gesture.Tap()...}> to run it on the UI thread.`,category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-reanimated-4-no-legacy-spring-thresholds`,id:`rn-reanimated-4-no-legacy-spring-thresholds`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-reanimated-4-no-legacy-spring-thresholds`,title:`Legacy Reanimated spring threshold`,severity:`warn`,recommendation:"Replace Reanimated 3 rest thresholds with Reanimated 4's `energyThreshold` spring option.",category:`Bugs`,framework:`react-native`,requires:[`reanimated:4`],tags:[`react-native`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/rn-reanimated-4-no-removed-api`,id:`rn-reanimated-4-no-removed-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-reanimated-4-no-removed-api`,title:`API removed in Reanimated 4`,severity:`warn`,recommendation:`Migrate removed Reanimated APIs to their Reanimated 4 or Gesture API replacements before upgrading.`,category:`Bugs`,framework:`react-native`,requires:[`reanimated:4`],tags:[`react-native`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/rn-reanimated-4-use-worklets-scheduler`,id:`rn-reanimated-4-use-worklets-scheduler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-reanimated-4-use-worklets-scheduler`,title:`Scheduler moved to Worklets`,severity:`warn`,recommendation:"Import the corresponding scheduler from `react-native-worklets` when migrating to Reanimated 4.",category:`Bugs`,framework:`react-native`,requires:[`reanimated:4`],tags:[`react-native`,`migration-hint`],isScanRule:!1}},{key:`react-doctor/rn-scrollview-dynamic-padding`,id:`rn-scrollview-dynamic-padding`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-scrollview-dynamic-padding`,title:`Dynamic padding on contentContainerStyle`,severity:`warn`,recommendation:"Use `contentInset={{ bottom: dynamicValue }}` so the OS shifts the content instead of relaying it out, which avoids the jump.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-scrollview-flex-in-content-container`,id:`rn-scrollview-flex-in-content-container`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-scrollview-flex-in-content-container`,title:`flex on contentContainerStyle collapses container`,severity:`warn`,recommendation:"In RN, `flex: 1` on `contentContainerStyle` sets `flexBasis: 0` and can collapse the container on small screens. Use `flexGrow: 1` instead.",category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/rn-style-prefer-boxshadow`,id:`rn-style-prefer-boxshadow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`rn-style-prefer-boxshadow`,title:`Platform-specific shadow keys over boxShadow`,severity:`warn`,recommendation:'These shadow keys only work on one platform. On the New Architecture, use a CSS `boxShadow` string like `boxShadow: "0 2px 8px rgba(0,0,0,0.1)"`, which works on both.',category:`Bugs`,framework:`react-native`,requires:[`react-native`],tags:[`react-native`,`test-noise`],isScanRule:!1}},{key:`react-doctor/role-button-requires-complete-keyboard-activation`,id:`role-button-requires-complete-keyboard-activation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`role-button-requires-complete-keyboard-activation`,title:`ARIA button handles only one activation key`,severity:`warn`,recommendation:`Use a native button, or make a custom role button activate with both Enter and Space.`,category:`Accessibility`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/role-has-required-aria-props`,id:`role-has-required-aria-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`role-has-required-aria-props`,title:`Role missing required ARIA props`,severity:`error`,recommendation:"Add every required `aria-*` attribute so assistive tech can expose the role's state correctly.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/role-supports-aria-props`,id:`role-supports-aria-props`,source:`react-doctor`,originallyExternal:!0,rule:{id:`role-supports-aria-props`,title:`Unsupported ARIA prop for role`,severity:`warn`,recommendation:"Only use `aria-*` attributes that the element's role supports.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/rules-of-hooks`,id:`rules-of-hooks`,source:`react-doctor`,originallyExternal:!0,rule:{id:`rules-of-hooks`,title:`Hook called conditionally`,severity:`error`,recommendation:`Call hooks at the top level of a React function component or custom Hook so React sees the same hook order on every render.`,category:`Bugs`,framework:`global`,requires:[`react`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/scope`,id:`scope`,source:`react-doctor`,originallyExternal:!0,rule:{id:`scope`,title:`scope attribute on non-th element`,severity:`warn`,recommendation:"Remove `scope` from this element or move it to the related `<th>` cell.",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/secret-in-fallback`,id:`secret-in-fallback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`secret-in-fallback`,title:`Hardcoded secret fallback for env var`,severity:`error`,recommendation:"Remove the literal fallback and fail closed (throw when the variable is unset). The hardcoded value is a committed secret, and the `??`/`||` default makes the app run with it in any environment that forgot to set the var.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/self-closing-comp`,id:`self-closing-comp`,source:`react-doctor`,originallyExternal:!0,rule:{id:`self-closing-comp`,title:`Element not self-closing`,severity:`warn`,recommendation:"Use `<X />` for childless elements so empty closing tags do not add noise.",category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/server-after-nonblocking`,id:`server-after-nonblocking`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-after-nonblocking`,title:`Blocking side effect before response`,severity:`warn`,recommendation:"`import { after } from 'next/server'`, then wrap it: `after(() => analytics.track(...))`. The response sends right away.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-auth-actions`,id:`server-auth-actions`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-auth-actions`,title:`Unauthenticated server action can be called directly`,severity:`error`,recommendation:`Check auth before changing server state or invoking billable services because exported server actions can be called directly by unauthenticated clients.`,category:`Bugs`,framework:`global`,tags:[`server-action`],isScanRule:!1}},{key:`react-doctor/server-cache-with-object-literal`,id:`server-cache-with-object-literal`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-cache-with-object-literal`,title:`React.cache with object literal`,severity:`warn`,recommendation:"Pass plain values like strings or numbers, not an object. React.cache() matches the exact value, so a new `{}` each render misses the cache.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-dedup-props`,id:`server-dedup-props`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-dedup-props`,title:`Duplicate data in server props`,severity:`warn`,recommendation:`Pass the source array once and build the other version on the client. Sending both ships the data twice to the browser.`,category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-fetch-without-revalidate`,id:`server-fetch-without-revalidate`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-fetch-without-revalidate`,title:`Fetch without revalidate`,severity:`warn`,recommendation:'Pass `{ next: { revalidate: <seconds> } }` (or `cache: "no-store"`) so old data doesn\'t stick around.',category:`Bugs`,framework:`global`,disabledWhen:[`nextjs:15`,`nextjs:static-export`],tags:[`server-action`],isScanRule:!1}},{key:`react-doctor/server-hoist-static-io`,id:`server-hoist-static-io`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-hoist-static-io`,title:`Static file read on every request`,severity:`warn`,recommendation:"Move the read to module scope so it runs once at load: `const FONT_DATA = await fetch(new URL('./fonts/Inter.ttf', import.meta.url)).then(r => r.arrayBuffer())`.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/server-no-mutable-module-state`,id:`server-no-mutable-module-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-no-mutable-module-state`,title:`Mutable module state on the server`,severity:`error`,recommendation:"Keep per-request data inside the action, or in headers, cookies, or `React.cache`. Module-scope `let`/`var` is shared by every request.",category:`Bugs`,framework:`global`,tags:[`server-action`],isScanRule:!1}},{key:`react-doctor/server-sequential-independent-await`,id:`server-sequential-independent-await`,source:`react-doctor`,originallyExternal:!1,rule:{id:`server-sequential-independent-await`,title:`Sequential independent awaits`,severity:`warn`,recommendation:"These two awaits don't depend on each other. Wrap them in `Promise.all([...])` so they run at the same time.",category:`Bugs`,framework:`global`,tags:[`server-action`,`test-noise`],isScanRule:!1}},{key:`react-doctor/shadcn-tabs-trigger-requires-list`,id:`shadcn-tabs-trigger-requires-list`,source:`react-doctor`,originallyExternal:!1,rule:{id:`shadcn-tabs-trigger-requires-list`,title:`Tabs trigger is outside TabsList`,severity:`warn`,recommendation:`Render each imported TabsTrigger inside its library TabsList so keyboard navigation and tablist semantics share one scope.`,category:`Bugs`,framework:`global`,defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/state-in-constructor`,id:`state-in-constructor`,source:`react-doctor`,originallyExternal:!0,rule:{id:`state-in-constructor`,title:`State initialized in constructor`,severity:`warn`,recommendation:`Use one class-state setup pattern so readers know where initial state lives.`,category:`Maintainability`,framework:`global`,requires:[`react`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/style-prop-object`,id:`style-prop-object`,source:`react-doctor`,originallyExternal:!0,rule:{id:`style-prop-object`,title:`Style prop is not an object`,severity:`warn`,recommendation:"Pass `style` as an object so React can apply CSS properties instead of ignoring a string style value.",category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/styled-components-duplicate-css-property-in-block`,id:`styled-components-duplicate-css-property-in-block`,source:`react-doctor`,originallyExternal:!1,rule:{id:`styled-components-duplicate-css-property-in-block`,title:`Duplicate CSS property in styled block`,severity:`warn`,recommendation:`Merge repeated declarations of the same CSS property in a styled block into one, so a later conditional value doesn't silently override an earlier one.`,category:`Maintainability`,framework:`global`,requires:[`styled-components`],tags:[`design`],defaultEnabled:!1,isScanRule:!1}},{key:`react-doctor/styled-components-non-transient-custom-prop-on-intrinsic-element`,id:`styled-components-non-transient-custom-prop-on-intrinsic-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`styled-components-non-transient-custom-prop-on-intrinsic-element`,title:`Non-transient custom prop on styled intrinsic element`,severity:`warn`,recommendation:"Prefix custom styled-components props with `$` (e.g. `$active`) so styled-components v6 keeps them off the DOM node instead of forwarding them as invalid attributes.",category:`Bugs`,framework:`global`,requires:[`styled-components:6`],isScanRule:!1}},{key:`react-doctor/supabase-client-owned-authz-field`,id:`supabase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{id:`supabase-client-owned-authz-field`,title:`Client writes Supabase authorization field`,severity:`error`,recommendation:"Use RLS policies based on `auth.uid()` and server-owned membership rows; do not trust client-provided owner, org, or role columns.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/supabase-rls-policy-risk`,id:`supabase-rls-policy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`supabase-rls-policy-risk`,title:`Permissive Supabase RLS policy`,severity:`error`,recommendation:"Keep public-read policies explicit, but gate inserts, updates, deletes, and service-role bypasses behind `auth.uid()` plus trusted tenant membership.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/supabase-table-missing-rls`,id:`supabase-table-missing-rls`,source:`react-doctor`,originallyExternal:!1,rule:{id:`supabase-table-missing-rls`,title:`Supabase table created without Row Level Security`,severity:`error`,recommendation:"Enable RLS in the same migration (`alter table <name> enable row level security;`) and add `auth.uid()`-scoped policies for select/insert/update/delete. A public table without RLS is fully readable and writable with the public anon key.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/svg-filter-clickjacking-risk`,id:`svg-filter-clickjacking-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`svg-filter-clickjacking-risk`,title:`SVG-filtered iframe clickjacking primitive`,severity:`warn`,recommendation:"Avoid filtering cross-origin iframes. Use `frame-ancestors` on sensitive pages and keep SVG filters away from embedded privileged UI.",category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/tabindex-no-positive`,id:`tabindex-no-positive`,source:`react-doctor`,originallyExternal:!0,rule:{id:`tabindex-no-positive`,title:`Positive tabindex value`,severity:`warn`,recommendation:"Use `tabIndex={0}` (focusable in source order) or `tabIndex={-1}` (focus only in code).",category:`Accessibility`,framework:`global`,requires:[`react`],tags:[`react-jsx-only`],isScanRule:!1}},{key:`react-doctor/tanstack-start-get-mutation`,id:`tanstack-start-get-mutation`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-get-mutation`,title:`Mutation in GET server function`,severity:`warn`,recommendation:"Use `createServerFn({ method: 'POST' })` for data changes. GET requests can be prefetched and are open to CSRF.",category:`Security`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-loader-parallel-fetch`,id:`tanstack-start-loader-parallel-fetch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-loader-parallel-fetch`,title:`Sequential awaits in loader`,severity:`warn`,recommendation:"Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to avoid request waterfalls in route loaders",category:`Performance`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-missing-head-content`,id:`tanstack-start-missing-head-content`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-missing-head-content`,title:`Root route missing HeadContent`,severity:`warn`,recommendation:"Add `<HeadContent />` inside `<head>` in your __root route. Without it, route `head()` meta tags are dropped.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-missing-scripts`,id:`tanstack-start-missing-scripts`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-missing-scripts`,title:`Root route missing Scripts`,severity:`warn`,recommendation:"Render `<Scripts />` near the end of `<body>` in your __root route so TanStack Start can load client-side JavaScript.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-anchor-element`,id:`tanstack-start-no-anchor-element`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-anchor-element`,title:`Plain anchor reloads TanStack Router navigation`,severity:`warn`,recommendation:"Use `Link` from `@tanstack/react-router` so internal navigation keeps client state, preloading, and typed routes.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-direct-fetch-in-loader`,id:`tanstack-start-no-direct-fetch-in-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-direct-fetch-in-loader`,title:`Direct fetch in route loader`,severity:`warn`,recommendation:"Use `createServerFn()` from @tanstack/react-start for type-safe RPC, input validation, and proper server/client code splitting",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-dynamic-server-fn-import`,id:`tanstack-start-no-dynamic-server-fn-import`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-dynamic-server-fn-import`,title:`Dynamic server function import`,severity:`error`,recommendation:"Use `import { myFn } from '~/utils/my.functions'`. The bundler only swaps server code for RPC stubs on static imports.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-navigate-in-render`,id:`tanstack-start-no-navigate-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-navigate-in-render`,title:`navigate() called during render`,severity:`warn`,recommendation:"Use `throw redirect({ to: '/path' })` in `beforeLoad` or `loader`. navigate() during render causes hydration issues.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-secrets-in-loader`,id:`tanstack-start-no-secrets-in-loader`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-secrets-in-loader`,title:`Secret exposed in a loader`,severity:`error`,recommendation:"Loaders are isomorphic (run on both server and client). Wrap secret access in `createServerFn()` so it stays server-only",category:`Security`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-use-server-in-handler`,id:`tanstack-start-no-use-server-in-handler`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-use-server-in-handler`,title:`Redundant use server in handler`,severity:`error`,recommendation:`TanStack Start handles server boundaries automatically via the Vite plugin. "use server" inside createServerFn causes compile errors.`,category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-no-useeffect-fetch`,id:`tanstack-start-no-useeffect-fetch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-no-useeffect-fetch`,title:`fetch inside useEffect in route`,severity:`warn`,recommendation:"Fetch data in the route `loader` instead. The router loads it before rendering and avoids waterfalls.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-redirect-in-try-catch`,id:`tanstack-start-redirect-in-try-catch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-redirect-in-try-catch`,title:`redirect() inside try-catch`,severity:`warn`,recommendation:"TanStack Router's `redirect()` and `notFound()` throw special errors caught by the router. Move them outside the try block or re-throw in the catch",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-route-property-order`,id:`tanstack-start-route-property-order`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-route-property-order`,title:`Route property order breaks type inference`,severity:`error`,recommendation:`Follow the route property order because TanStack Router's type inference depends on earlier properties feeding later ones.`,category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-server-fn-method-order`,id:`tanstack-start-server-fn-method-order`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-server-fn-method-order`,title:`Server function method order breaks type inference`,severity:`error`,recommendation:`Chain methods in order: .middleware() → .validator() → .client() → .server() → .handler(). Types depend on this sequence.`,category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tanstack-start-server-fn-validate-input`,id:`tanstack-start-server-fn-validate-input`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tanstack-start-server-fn-validate-input`,title:`Server function without input validation`,severity:`warn`,recommendation:"Add `.validator(schema)` before `.handler()`. This data crosses the network and must be validated at runtime.",category:`Bugs`,framework:`tanstack-start`,requires:[`tanstack-start`],tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/tenant-static-proxy-risk`,id:`tenant-static-proxy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`tenant-static-proxy-risk`,title:`Tenant-controlled static asset proxy`,severity:`warn`,recommendation:`Bind tenant identity to the trusted host or authenticated org, canonicalize after decoding, reject traversal, and never let one tenant choose another tenant's asset prefix.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/three-cap-device-pixel-ratio`,id:`three-cap-device-pixel-ratio`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-cap-device-pixel-ratio`,title:`Unbounded Three.js device pixel ratio`,severity:`warn`,recommendation:`Cap renderer pixel ratio, commonly at 2, so high-density displays do not multiply GPU work without a bound`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-limit-shadowed-point-lights`,id:`three-limit-shadowed-point-lights`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-limit-shadowed-point-lights`,title:`Too many shadow-casting Three.js point lights`,severity:`warn`,recommendation:`Keep at most two shadow-casting point lights in one scene, or use cheaper directional, spot, baked, or fake shadows`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-allocation-in-pointer-move`,id:`three-no-allocation-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-allocation-in-pointer-move`,title:`Three.js allocation inside pointer-move handler`,severity:`warn`,recommendation:`Reuse vectors, raycasters, and other Three.js objects while handling continuous pointer movement`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-async-animation-loop`,id:`three-no-async-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-async-animation-loop`,title:`Async Three.js animation callback`,severity:`warn`,recommendation:`Keep animation callbacks synchronous; start asynchronous work outside the loop and consume completed state during frames`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-clone-in-animation-loop`,id:`three-no-clone-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-clone-in-animation-loop`,title:`Three.js clone inside animation loop`,severity:`warn`,recommendation:`Clone once before the animation loop or copy values into a reusable scratch object during frames`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-material-recompile-in-animation-loop`,id:`three-no-material-recompile-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-material-recompile-in-animation-loop`,title:`Material recompiled inside animation loop`,severity:`error`,recommendation:`Change material defines or features outside the animation loop and set needsUpdate only when the compiled program must change`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-new-in-animation-loop`,id:`three-no-new-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-new-in-animation-loop`,title:`Allocation inside Three.js animation loop`,severity:`warn`,recommendation:`Allocate reusable objects before the animation loop and mutate them in place during each frame`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-object-construction-in-render`,id:`three-no-object-construction-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-object-construction-in-render`,title:`Three.js object constructed during React render`,severity:`warn`,recommendation:`Construct mutable Three.js objects in a stable initializer, effect, event, or module scope instead of recreating them during React render`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-redundant-uniforms-need-update`,id:`three-no-redundant-uniforms-need-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-redundant-uniforms-need-update`,title:`Redundant uniformsNeedUpdate inside animation loop`,severity:`warn`,recommendation:`Mutate ShaderMaterial uniform values directly; Three.js uploads custom uniforms during rendering`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-state-in-animation-loop`,id:`three-no-state-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-state-in-animation-loop`,title:`React state update inside Three.js animation loop`,severity:`warn`,recommendation:`Mutate Three.js objects or refs during frames; reserve React state for guarded, infrequent transitions`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-no-state-in-pointer-move`,id:`three-no-state-in-pointer-move`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-no-state-in-pointer-move`,title:`React state update inside Three.js pointer-move handler`,severity:`warn`,recommendation:`Keep continuous pointer previews in Three.js objects or refs and publish React state when the interaction commits`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-on-before-compile-require-program-cache-key`,id:`three-on-before-compile-require-program-cache-key`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-on-before-compile-require-program-cache-key`,title:`onBeforeCompile variant lacks a program cache key`,severity:`error`,recommendation:`Return every mutable shader-patch variant from customProgramCacheKey on the same material`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-prefer-instanced-mesh`,id:`three-prefer-instanced-mesh`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-prefer-instanced-mesh`,title:`Repeated Three.js meshes use separate draw calls`,severity:`warn`,recommendation:`Replace repeated Mesh objects that share geometry and material with one InstancedMesh and per-instance transforms`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-raw-shader-require-fragment-float-precision`,id:`three-raw-shader-require-fragment-float-precision`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-raw-shader-require-fragment-float-precision`,title:`Raw fragment shader lacks float precision`,severity:`error`,recommendation:`Declare a default fragment float precision or qualify every floating-point declaration`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-raw-shader-require-glsl3-version`,id:`three-raw-shader-require-glsl3-version`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-raw-shader-require-glsl3-version`,title:`Raw shader uses GLSL 3 syntax without GLSL3`,severity:`error`,recommendation:`Set glslVersion to THREE.GLSL3 when RawShaderMaterial uses GLSL 3 syntax`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-animation-mixer-cleanup`,id:`three-require-animation-mixer-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-animation-mixer-cleanup`,title:`Unreleased Three.js animation mixer actions`,severity:`warn`,recommendation:`Stop component-owned AnimationMixer actions and uncache their owned root in React cleanup`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-controls-cleanup`,id:`three-require-controls-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-controls-cleanup`,title:`Undisposed imperative Three.js controls`,severity:`warn`,recommendation:`Dispose component-owned Three.js controls in a React cleanup so their DOM listeners are removed`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-frame-delta`,id:`three-require-frame-delta`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-frame-delta`,title:`Frame-rate-dependent Three.js animation`,severity:`warn`,recommendation:`Scale incremental transforms and interpolation by Clock.getDelta(), use delta-aware damping, or assign from absolute animation time`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-instanced-buffer-update`,id:`three-require-instanced-buffer-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-instanced-buffer-update`,title:`Three.js instanced mesh buffer is not marked for upload`,severity:`error`,recommendation:`After setMatrixAt or setColorAt, set the matching instance buffer's needsUpdate flag to true`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-owned-geometry-cleanup`,id:`three-require-owned-geometry-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-owned-geometry-cleanup`,title:`Locally owned Three.js geometry is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed geometries when their React owner releases them`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-owned-material-cleanup`,id:`three-require-owned-material-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-owned-material-cleanup`,title:`Locally owned Three.js material is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed materials when their React owner releases them`,category:`Performance`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-owned-texture-cleanup`,id:`three-require-owned-texture-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-owned-texture-cleanup`,title:`Locally owned Three.js texture is not disposed`,severity:`warn`,recommendation:`Dispose locally constructed textures when their React owner releases them`,category:`Performance`,framework:`global`,requires:[`react`,`three`],disabledWhen:[`r3f`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-postprocessing-cleanup`,id:`three-require-postprocessing-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-postprocessing-cleanup`,title:`Undisposed Three.js postprocessing resource`,severity:`warn`,recommendation:`Dispose component-owned postprocessing composers and resource-owning passes`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-projection-matrix-update`,id:`three-require-projection-matrix-update`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-projection-matrix-update`,title:`Missing Three.js camera projection-matrix update`,severity:`error`,recommendation:`Call camera.updateProjectionMatrix() after changing projection properties so Three.js renders the new frustum`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-render-target-cleanup`,id:`three-require-render-target-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-render-target-cleanup`,title:`Undisposed Three.js render target`,severity:`warn`,recommendation:`Dispose component-owned render targets in a React cleanup that follows the render target binding`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-require-renderer-cleanup`,id:`three-require-renderer-cleanup`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-require-renderer-cleanup`,title:`Undisposed Three.js renderer`,severity:`warn`,recommendation:`Dispose component-owned renderers and stop their animation loop or animation frame in matching React cleanup`,category:`Bugs`,framework:`global`,requires:[`react`,`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-constant-out-of-bounds-index`,id:`three-shader-no-constant-out-of-bounds-index`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-constant-out-of-bounds-index`,title:`Shader uses a constant out-of-bounds index`,severity:`error`,recommendation:`Keep constant array, vector, and matrix indices within their declared bounds`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-derivatives-in-nonuniform-flow`,id:`three-shader-no-derivatives-in-nonuniform-flow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-derivatives-in-nonuniform-flow`,title:`Shader derivative runs in non-uniform control flow`,severity:`error`,recommendation:`Evaluate derivatives and implicit-LOD texture samples before fragment-input-dependent branches`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-clamp-bounds`,id:`three-shader-no-invalid-clamp-bounds`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-clamp-bounds`,title:`Shader uses invalid clamp bounds`,severity:`error`,recommendation:`Keep clamp minVal less than or equal to maxVal`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-constant-bit-operations`,id:`three-shader-no-invalid-constant-bit-operations`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-constant-bit-operations`,title:`Shader uses invalid constant bit operations`,severity:`error`,recommendation:`Keep shift counts and bitfield ranges within the integer bit width`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-constant-math`,id:`three-shader-no-invalid-constant-math`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-constant-math`,title:`Shader uses invalid constant math`,severity:`error`,recommendation:`Keep constant arguments within the defined GLSL builtin and operator domains`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-invalid-smoothstep-edges`,id:`three-shader-no-invalid-smoothstep-edges`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-invalid-smoothstep-edges`,title:`Shader uses invalid smoothstep edges`,severity:`error`,recommendation:`Keep smoothstep edge0 below edge1 and invert the result when a descending transition is needed`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-inverse-of-uniform`,id:`three-shader-no-inverse-of-uniform`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-inverse-of-uniform`,title:`Shader inverts a uniform matrix per invocation`,severity:`warn`,recommendation:`Compute uniform matrix inverses once on the CPU and upload the result`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-redeclared-builtins`,id:`three-shader-no-redeclared-builtins`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-redeclared-builtins`,title:`ShaderMaterial redeclares a Three.js builtin`,severity:`error`,recommendation:`Use ShaderMaterial built-in attributes and uniforms without redeclaring them`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-redundant-frag-depth`,id:`three-shader-no-redundant-frag-depth`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-redundant-frag-depth`,title:`Shader redundantly writes fragment depth`,severity:`warn`,recommendation:`Let fixed-function depth supply gl_FragCoord.z when depth is unchanged`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-no-version-directive`,id:`three-shader-no-version-directive`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-no-version-directive`,title:`Three shader source contains a version directive`,severity:`error`,recommendation:`Set glslVersion on the material instead of writing #version in shader source`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-prefer-small-integer-pow`,id:`three-shader-prefer-small-integer-pow`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-prefer-small-integer-pow`,title:`Shader uses pow for a small integer exponent`,severity:`warn`,recommendation:`Use explicit multiplication for powers of two, three, or four`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-prefer-squared-distance-comparison`,id:`three-shader-prefer-squared-distance-comparison`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-prefer-squared-distance-comparison`,title:`Shader compares a computed distance`,severity:`warn`,recommendation:`Compare squared vector distance against a squared nonnegative threshold`,category:`Performance`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-matching-uniforms`,id:`three-shader-require-matching-uniforms`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-matching-uniforms`,title:`Shader stages declare incompatible uniforms`,severity:`error`,recommendation:`Use the same type, array dimensions, and precision for uniforms shared by both shader stages`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-matching-varyings`,id:`three-shader-require-matching-varyings`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-matching-varyings`,title:`Shader stage interface does not match`,severity:`error`,recommendation:`Declare every statically used fragment input as a compatible vertex output with the same name`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-position-on-all-paths`,id:`three-shader-require-position-on-all-paths`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-position-on-all-paths`,title:`Vertex shader leaves gl_Position undefined`,severity:`error`,recommendation:`Assign gl_Position before every return and on every path through main`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-shader-require-uniform-bindings`,id:`three-shader-require-uniform-bindings`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-shader-require-uniform-bindings`,title:`Shader uniform has no material binding`,severity:`error`,recommendation:`Add every custom GLSL uniform to the ShaderMaterial uniforms object with a compatible value`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-tsl-no-js-uniform-branch`,id:`three-tsl-no-js-uniform-branch`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-tsl-no-js-uniform-branch`,title:`JavaScript branch reads a TSL uniform value`,severity:`warn`,recommendation:`Express uniform-dependent shader control flow with TSL If, select, or Loop nodes so it runs on the GPU`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-webgpu-no-legacy-effect-composer`,id:`three-webgpu-no-legacy-effect-composer`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-webgpu-no-legacy-effect-composer`,title:`Legacy EffectComposer used with WebGPURenderer`,severity:`error`,recommendation:`Use WebGPURenderer's node-based post-processing pipeline instead of the legacy WebGL EffectComposer`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/three-webgpu-no-legacy-material-api`,id:`three-webgpu-no-legacy-material-api`,source:`react-doctor`,originallyExternal:!1,rule:{id:`three-webgpu-no-legacy-material-api`,title:`Legacy material API used with WebGPURenderer`,severity:`error`,recommendation:`Use Three.js node materials and TSL for custom shaders rendered by WebGPURenderer`,category:`Bugs`,framework:`global`,requires:[`three`],tags:[`three`,`webgl`],isScanRule:!1}},{key:`react-doctor/unsafe-json-in-html`,id:`unsafe-json-in-html`,source:`react-doctor`,originallyExternal:!1,rule:{id:`unsafe-json-in-html`,title:`Unescaped JSON in HTML or script sink`,severity:`warn`,recommendation:'JSON.stringify does not HTML-escape, so a `<\/script>` (or `<`) in the data breaks out and becomes XSS. Use an HTML-safe serializer (serialize-javascript, devalue) or escape `<`, `>`, and `&`, or pass data via a JSON `<script type="application/json">` read with JSON.parse.',category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/untrusted-redirect-following`,id:`untrusted-redirect-following`,source:`react-doctor`,originallyExternal:!1,rule:{id:`untrusted-redirect-following`,title:`Server fetch follows redirects for caller-shaped URL`,severity:`warn`,recommendation:'Use `redirect: "manual"` or equivalent and re-validate every redirect target before following it to avoid SSRF redirect bypasses.',category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/url-prefilled-privileged-action`,id:`url-prefilled-privileged-action`,source:`react-doctor`,originallyExternal:!1,rule:{id:`url-prefilled-privileged-action`,title:`URL pre-fills a privileged action`,severity:`warn`,recommendation:`Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/use-lazy-motion`,id:`use-lazy-motion`,source:`react-doctor`,originallyExternal:!1,rule:{id:`use-lazy-motion`,title:`Full Framer Motion import`,severity:`warn`,recommendation:'Use `import { LazyMotion, m } from "framer-motion"` with `domAnimation` features. Saves about 30kb.',category:`Performance`,framework:`global`,tags:[`test-noise`],isScanRule:!1}},{key:`react-doctor/valtio-no-proxy-read-in-render`,id:`valtio-no-proxy-read-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`valtio-no-proxy-read-in-render`,title:`Valtio proxy read during render`,severity:`warn`,recommendation:`Read reactive render values from useSnapshot. Reserve the mutable proxy for event handlers, effects, and other callbacks that need the latest value.`,category:`Bugs`,framework:`global`,requires:[`react`,`valtio:1`],isScanRule:!1}},{key:`react-doctor/valtio-no-snapshot-in-callback`,id:`valtio-no-snapshot-in-callback`,source:`react-doctor`,originallyExternal:!1,rule:{id:`valtio-no-snapshot-in-callback`,title:`Valtio snapshot read in a callback`,severity:`warn`,recommendation:"Read from the original Valtio proxy inside callbacks. `useSnapshot()` results are for render reads; callback reads can become tracked render dependencies and cause extra re-renders.",category:`Bugs`,framework:`global`,requires:[`react`,`valtio`,`valtio:1`],isScanRule:!1}},{key:`react-doctor/void-dom-elements-no-children`,id:`void-dom-elements-no-children`,source:`react-doctor`,originallyExternal:!0,rule:{id:`void-dom-elements-no-children`,title:`Children on a void element`,severity:`warn`,recommendation:`Remove the children or use a non-void tag so React does not drop content the element cannot render.`,category:`Bugs`,framework:`global`,requires:[`react`],isScanRule:!1}},{key:`react-doctor/waapi-animation-in-render`,id:`waapi-animation-in-render`,source:`react-doctor`,originallyExternal:!1,rule:{id:`waapi-animation-in-render`,title:`Web Animation starts during render`,severity:`error`,recommendation:`Start element.animate() from an effect or interaction handler so React renders stay pure.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/web-animation-offsets-valid`,id:`web-animation-offsets-valid`,source:`react-doctor`,originallyExternal:!1,rule:{id:`web-animation-offsets-valid`,title:`Invalid Web Animation keyframe offsets`,severity:`error`,recommendation:`Keep explicit Web Animation keyframe offsets between 0 and 1 and in nondecreasing order.`,category:`Bugs`,framework:`global`,isScanRule:!1}},{key:`react-doctor/webgl-no-sync-readback-in-animation-loop`,id:`webgl-no-sync-readback-in-animation-loop`,source:`react-doctor`,originallyExternal:!1,rule:{id:`webgl-no-sync-readback-in-animation-loop`,title:`Synchronous GPU readback inside animation loop`,severity:`warn`,recommendation:`Move GPU readback to a discrete or asynchronous path and reuse the latest completed result during frames`,category:`Performance`,framework:`global`,tags:[`webgl`],isScanRule:!1}},{key:`react-doctor/webhook-signature-risk`,id:`webhook-signature-risk`,source:`react-doctor`,originallyExternal:!1,rule:{id:`webhook-signature-risk`,title:`Webhook handler lacks signature verification`,severity:`warn`,recommendation:`Verify provider signatures before parsing or acting on webhook bodies. Use provider SDK helpers or HMAC verification with timing-safe comparison.`,category:`Security`,framework:`global`,tags:[`security-scan`],isScanRule:!0}},{key:`react-doctor/window-open-without-noopener`,id:`window-open-without-noopener`,source:`react-doctor`,originallyExternal:!1,rule:{id:`window-open-without-noopener`,title:`window.open without noopener`,severity:`warn`,recommendation:"Pass `'noopener'` in the third features argument of `window.open` so the opened page can't control your tab through `window.opener`. Add `'noreferrer'` too when the destination must not receive the referrer.",category:`Security`,framework:`global`,isScanRule:!1}},{key:`react-doctor/zod-v4-no-deprecated-error-apis`,id:`zod-v4-no-deprecated-error-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-no-deprecated-error-apis`,title:`Zod 3 error API breaks in Zod 4`,severity:`warn`,recommendation:"Use the Zod 4 helpers instead: `z.treeifyError()`, `z.flattenError()`, `z.prettifyError()`, or read `error.issues` directly.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zod-v4-no-deprecated-error-customization`,id:`zod-v4-no-deprecated-error-customization`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-no-deprecated-error-customization`,title:`Zod 3 error customization breaks in Zod 4`,severity:`warn`,recommendation:"In Zod 4, use the single `{ error }` option instead of message strings, `invalid_type_error`, `required_error`, or `errorMap`.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zod-v4-no-deprecated-schema-apis`,id:`zod-v4-no-deprecated-schema-apis`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-no-deprecated-schema-apis`,title:`Zod 3 schema API breaks in Zod 4`,severity:`warn`,recommendation:"Switch to the Zod 4 versions: top-level factories like `z.enum()`, object helpers like `z.strictObject()`, the new `z.function({ input, output })` form, and explicit key/value schemas for `z.record()`.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zod-v4-prefer-top-level-string-formats`,id:`zod-v4-prefer-top-level-string-formats`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zod-v4-prefer-top-level-string-formats`,title:`Format method on z.string()`,severity:`warn`,recommendation:"Use the Zod 4 top-level format checks like `z.email()`, `z.uuid()`, or `z.ipv4()` instead of `z.string().<format>()`.",category:`Maintainability`,framework:`global`,requires:[`zod:4`],tags:[`migration-hint`],isScanRule:!1}},{key:`react-doctor/zustand-no-fresh-selector-result`,id:`zustand-no-fresh-selector-result`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-fresh-selector-result`,title:`Zustand selector returns a fresh value`,severity:`error`,recommendation:"Select a stable store field, split the selector, or wrap a collection selector with `useShallow`.",category:`Performance`,framework:`global`,requires:[`react`,`zustand`,`zustand:5`],isScanRule:!1}},{key:`react-doctor/zustand-no-get-during-initialization`,id:`zustand-no-get-during-initialization`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-get-during-initialization`,title:`Zustand get() called during store initialization`,severity:`error`,recommendation:"Move the read into a deferred store action or derive the initial value without calling `get()`.",category:`Bugs`,framework:`global`,requires:[`react`,`zustand`,`zustand:1`],isScanRule:!1}},{key:`react-doctor/zustand-no-mutating-state`,id:`zustand-no-mutating-state`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-mutating-state`,title:`Zustand state mutated in place`,severity:`error`,recommendation:`Create a new object, array, Map, or Set before passing the updated value to Zustand.`,category:`Bugs`,framework:`global`,requires:[`react`,`zustand`,`zustand:1`],isScanRule:!1}},{key:`react-doctor/zustand-no-whole-store-destructure`,id:`zustand-no-whole-store-destructure`,source:`react-doctor`,originallyExternal:!1,rule:{id:`zustand-no-whole-store-destructure`,title:`Whole Zustand store subscribed during render`,severity:`warn`,recommendation:`Pass a selector to the Zustand hook so this component rerenders only when the state it reads changes.`,category:`Bugs`,framework:`global`,requires:[`react`,`zustand:1`],isScanRule:!1}}];const oe=e=>{if(e===`client-secret`)return ee;if(e===`static-export-redirect`)return y},$=ae.map(e=>{let{recommendationOverride:t,...n}=e.rule,r=oe(t);return{...e,rule:{...n,...r?{recommendationFor:r}:{}}}}),se=Object.fromEntries($.map(e=>[e.id,e.rule])),ce=[{key:`react-doctor/active-static-asset`,id:`active-static-asset`,source:`react-doctor`,originallyExternal:!1,rule:{...n,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...n.tags??[]])]}},{key:`react-doctor/agent-tool-capability-risk`,id:`agent-tool-capability-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...Q,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...Q.tags??[]])]}},{key:`react-doctor/artifact-baas-authority-surface`,id:`artifact-baas-authority-surface`,source:`react-doctor`,originallyExternal:!1,rule:{..._,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,..._.tags??[]])]}},{key:`react-doctor/artifact-env-leak`,id:`artifact-env-leak`,source:`react-doctor`,originallyExternal:!1,rule:{...z,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...z.tags??[]])]}},{key:`react-doctor/artifact-secret-leak`,id:`artifact-secret-leak`,source:`react-doctor`,originallyExternal:!1,rule:{...O,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...O.tags??[]])]}},{key:`react-doctor/build-pipeline-secret-boundary`,id:`build-pipeline-secret-boundary`,source:`react-doctor`,originallyExternal:!1,rule:{...P,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...P.tags??[]])]}},{key:`react-doctor/clickjacking-redirect-risk`,id:`clickjacking-redirect-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...H,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...H.tags??[]])]}},{key:`react-doctor/command-execution-input-risk`,id:`command-execution-input-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...R,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...R.tags??[]])]}},{key:`react-doctor/cors-cookie-trust-risk`,id:`cors-cookie-trust-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...G,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...G.tags??[]])]}},{key:`react-doctor/dangerous-html-sink`,id:`dangerous-html-sink`,source:`react-doctor`,originallyExternal:!1,rule:{...c,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...c.tags??[]])]}},{key:`react-doctor/firebase-client-owned-authz-field`,id:`firebase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{...w,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...w.tags??[]])]}},{key:`react-doctor/firebase-permissive-rules`,id:`firebase-permissive-rules`,source:`react-doctor`,originallyExternal:!1,rule:{...S,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...S.tags??[]])]}},{key:`react-doctor/firebase-query-filter-as-auth`,id:`firebase-query-filter-as-auth`,source:`react-doctor`,originallyExternal:!1,rule:{...l,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...l.tags??[]])]}},{key:`react-doctor/git-provider-url-injection-risk`,id:`git-provider-url-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...C,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...C.tags??[]])]}},{key:`react-doctor/import-metadata-execution-risk`,id:`import-metadata-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...t,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...t.tags??[]])]}},{key:`react-doctor/insecure-crypto-risk`,id:`insecure-crypto-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...Z,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...Z.tags??[]])]}},{key:`react-doctor/insecure-session-cookie`,id:`insecure-session-cookie`,source:`react-doctor`,originallyExternal:!1,rule:{...g,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...g.tags??[]])]}},{key:`react-doctor/jwt-insecure-verification`,id:`jwt-insecure-verification`,source:`react-doctor`,originallyExternal:!1,rule:{...d,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...d.tags??[]])]}},{key:`react-doctor/key-lifecycle-risk`,id:`key-lifecycle-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...u,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...u.tags??[]])]}},{key:`react-doctor/local-rpc-native-bridge-risk`,id:`local-rpc-native-bridge-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...s,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...s.tags??[]])]}},{key:`react-doctor/mcp-tool-capability-risk`,id:`mcp-tool-capability-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...h,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...h.tags??[]])]}},{key:`react-doctor/mdx-ssr-execution-risk`,id:`mdx-ssr-execution-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...p,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...p.tags??[]])]}},{key:`react-doctor/nosql-injection-risk`,id:`nosql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...f,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...f.tags??[]])]}},{key:`react-doctor/package-metadata-secret`,id:`package-metadata-secret`,source:`react-doctor`,originallyExternal:!1,rule:{...F,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...F.tags??[]])]}},{key:`react-doctor/path-traversal-risk`,id:`path-traversal-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...e,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...e.tags??[]])]}},{key:`react-doctor/plugin-update-trust-risk`,id:`plugin-update-trust-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...I,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...I.tags??[]])]}},{key:`react-doctor/postmessage-origin-risk`,id:`postmessage-origin-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...m,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...m.tags??[]])]}},{key:`react-doctor/public-debug-artifact`,id:`public-debug-artifact`,source:`react-doctor`,originallyExternal:!1,rule:{...i,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...i.tags??[]])]}},{key:`react-doctor/public-env-secret-name`,id:`public-env-secret-name`,source:`react-doctor`,originallyExternal:!1,rule:{...a,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...a.tags??[]])]}},{key:`react-doctor/raw-sql-injection-risk`,id:`raw-sql-injection-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...b,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...b.tags??[]])]}},{key:`react-doctor/repository-secret-file`,id:`repository-secret-file`,source:`react-doctor`,originallyExternal:!1,rule:{...q,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...q.tags??[]])]}},{key:`react-doctor/request-body-mass-assignment`,id:`request-body-mass-assignment`,source:`react-doctor`,originallyExternal:!1,rule:{...r,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...r.tags??[]])]}},{key:`react-doctor/secret-in-fallback`,id:`secret-in-fallback`,source:`react-doctor`,originallyExternal:!1,rule:{...v,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...v.tags??[]])]}},{key:`react-doctor/supabase-client-owned-authz-field`,id:`supabase-client-owned-authz-field`,source:`react-doctor`,originallyExternal:!1,rule:{...k,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...k.tags??[]])]}},{key:`react-doctor/supabase-rls-policy-risk`,id:`supabase-rls-policy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...X,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...X.tags??[]])]}},{key:`react-doctor/supabase-table-missing-rls`,id:`supabase-table-missing-rls`,source:`react-doctor`,originallyExternal:!1,rule:{...K,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...K.tags??[]])]}},{key:`react-doctor/svg-filter-clickjacking-risk`,id:`svg-filter-clickjacking-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...D,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...D.tags??[]])]}},{key:`react-doctor/tenant-static-proxy-risk`,id:`tenant-static-proxy-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...j,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...j.tags??[]])]}},{key:`react-doctor/unsafe-json-in-html`,id:`unsafe-json-in-html`,source:`react-doctor`,originallyExternal:!1,rule:{...M,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...M.tags??[]])]}},{key:`react-doctor/untrusted-redirect-following`,id:`untrusted-redirect-following`,source:`react-doctor`,originallyExternal:!1,rule:{...L,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...L.tags??[]])]}},{key:`react-doctor/url-prefilled-privileged-action`,id:`url-prefilled-privileged-action`,source:`react-doctor`,originallyExternal:!1,rule:{...B,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...B.tags??[]])]}},{key:`react-doctor/webhook-signature-risk`,id:`webhook-signature-risk`,source:`react-doctor`,originallyExternal:!1,rule:{...A,framework:`global`,category:`Security`,tags:[...new Set([`security-scan`,...A.tags??[]])]}}],le=$,ue=se,de=new Set($.map(e=>e.key)),fe=new Set($.filter(e=>e.rule.framework!==`global`).map(e=>e.key));export{de as ALL_REACT_DOCTOR_RULE_KEYS,T as CROSS_FILE_DEPENDENCY_COLLECTORS,W as CROSS_FILE_RULE_IDS,ne as EXTERNAL_RULES,fe as FRAMEWORK_SPECIFIC_RULE_KEYS,U as FRAMEWORK_TOKENS,ie as MOTION_LIBRARY_PACKAGES,Y as REACT_COMPILER_RULES,le as REACT_DOCTOR_RULES,ue as REACT_DOCTOR_RULE_REGISTRY,ce as REACT_DOCTOR_SCAN_RULES,J as REACT_NATIVE_DEPENDENCY_NAMES,x as REACT_NATIVE_DEPENDENCY_PREFIXES,te as UNBOUNDED_CROSS_FILE_RULE_IDS,re as classifySecurityScanFile,E as collectCrossFileDependencyProbes,o as isReactNativeDependencyName,N as resetManifestCaches,V as shouldReadSecurityScanContent};
14
14
  //# sourceMappingURL=core.js.map