Skip to content

chore(deps): bump vite-plus to v0.2.2#5

Draft
fengmk2 wants to merge 11 commits into
mainfrom
update-vite-plus-prerelease-test-0.2.2
Draft

chore(deps): bump vite-plus to v0.2.2#5
fengmk2 wants to merge 11 commits into
mainfrom
update-vite-plus-prerelease-test-0.2.2

Conversation

@fengmk2

@fengmk2 fengmk2 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Bump vite-plus and related packages to the pkg.pr.new prerelease build for v0.2.2 (registry-bridge commit build) to smoke-test the prerelease.

  • vite-plus + vite (alias to @voidzero-dev/vite-plus-core) and vitest pinned to the commit build across deps / overrides / catalogs
  • minimumReleaseAge enabled with the vite-plus / @voidzero-dev/* / oxc / oxlint stack excluded
  • .npmrc (or .yarnrc.yml) points the package manager at the registry bridge (prerelease scaffolding)

Test plan

  • CI passes

aidenybai and others added 11 commits June 29, 2026 20:19
…nco#1016)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…g helpers) (millionco#988)

* fix(rules): shared FP-hardening infra (test harness + cross-cutting helpers)

Foundation for the rule false-positive split off #986. Cross-cutting pieces
every domain build/test depends on:

- test-utils/attach-source-locations: attach `range` to AST nodes so
  eslint-scope-backed rules resolve correctly under the test harness.
- constants/react: shared hook/name tables used by ~10 rule domains.
- utils/contains-fetch-call, utils/build-same-file-memo-registry: shared
  detectors consumed by multiple domains.

No changeset (intentional).

* fix(rules): descend into synchronously invoked functions in containsFetchCall

The stopAtFunctionBoundary walk pruned every nested function, so effects
that kick off fetches via an async IIFE or a name-invoked inner function
(`async function loadData(){...} loadData()`, `const loadData = async
() => {...}; void loadData()`) were missed. Replace the blanket prune
with a fixed-point walk that descends only into functions the body
itself invokes, still skipping escaping callbacks like event handlers
and returned cleanups.

* fix(rules): drop `watch` from cleanup-returning subscription methods

Returning a `watch` handle is not cleanup: react-hook-form's
`form.watch(cb)` returns `{ unsubscribe }` (not callable) and
`fs.watch` returns an FSWatcher needing `.close()`. Keep `listen`,
whose returned disposer follows the `subscribe` contract, and pin the
behavior with effect-needs-cleanup regression tests.

* FP-FIX(a11y): eliminate false positives across accessibility rules (#989)

* fix(a11y): eliminate false positives across accessibility rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* refactor(a11y): deslop FP-fix rules (reuse helpers, de-nest, relocate getImplicitRole)

Behavior-preserving cleanup of the accessibility false-positive fixes:

- interactive-supports-focus: replace the inline spread-attribute check with
  the existing `hasJsxSpreadAttribute` util (drops a byte-for-byte
  reimplementation and a now-dead `isNodeOfType` import).
- no-redundant-roles: de-nest the implicit-role ternary into an if/else chain
  (AGENTS.md: no nested ternaries) and de-shadow the filter parameter.
- prefer-tag-over-role: merge the two disjoint role sets that were only ever
  used together in one boolean union into one `ROLES_WITHOUT_CLEAN_TAG`.
- role-supports-aria-props / no-redundant-roles: relocate `getImplicitRole`
  into `utils/get-implicit-role.ts`, removing the only rule-to-rule import in
  the a11y directory (shared helpers belong in utils/).
- control-has-associated-label: un-glue and tighten a comment block.

No behavior change: full oxlint-plugin suite (7380 tests) green; typecheck,
lint, and format all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(a11y): flag role={undefined} in no-noninteractive-element-interactions

`collectRoleBranches` marked `null`/`false` literals as role-less but ignored
the `undefined` Identifier, so `role={undefined}` — and ternary branches
resolving to it — fell through the empty-branch early return and silenced the
rule, a false negative on a genuinely role-less non-interactive element.

Treat the `undefined` identifier like a `null`/`false` literal
(`hasNonRoleBranch`), while leaving a genuinely opaque variable role
(`role={dynamicRole}`) silent. Adds regression coverage for both.

Addresses the Cursor Bugbot "Undefined role silences rule" finding on #989.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(a11y): treat spread children as content in objectHasAccessibleChild

`objectHasAccessibleChild` counted an explicit `children` prop as content but
not a `{...props}` spread, so `<h1 {...props} />` (forwardRef card titles,
markdown component overrides) and `<a {...props} />` were flagged as empty. The
RDE 500-repo run surfaced this as heading-has-content's dominant false positive
(~83% of sampled hits, all `<h_ {...props} />`).

Treat a JSX spread as possible children in the shared util — one fix covers
heading-has-content, anchor-has-content, and alt-text's <object> check —
matching the spread-bail pattern six sibling a11y rules already use. Genuinely
empty elements (no spread) are still flagged; a11y suite green (3206), no OXC
fixture divergence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rules): treat dot-prefixed tool dirs (.dumi, .storybook) as non-production in isTestlikeFilename

* fix(a11y): revert the ARIA-1.2 implicit-combobox upgrade in getImplicitRole (oxc parity)

* fix(a11y): exempt <label> and testlike files, harden role-expression analysis in no-noninteractive-element-interactions

* fix(a11y): drop the ref exemption in no-noninteractive-tabindex (keyboard handlers still exempt)

* fix(a11y): treat native checked state as intrinsic aria-checked and skip custom components in role-has-required-aria-props

* fix(a11y): count {" "} and static template children as anchor text, skip testlike files in anchor-has-content

* fix(a11y): only display-none classes exempt the programmatic file input in control-has-associated-label

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FP-FIX(performance): eliminate false positives across perf/js-performance/bundle-size rules (#994)

* fix(performance): eliminate false positives across perf/js-performance/bundle-size rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* fix(js-index-maps): don't treat nested-callback bindings as loop-varying

collectLoopBoundNames walked the whole loop subtree, so a binding declared
inside a nested callback could mark an outer loop-invariant .find() receiver of
the same name as loop-variant and suppress the finding. Prune nested function
scopes. (Bugbot)

* fix(performance): tighten dynamic-import prefix + Intl memo write detection

- no-dynamic-import-path: a relative prefix only counts as static when it has a
  real directory segment before the first hole. `./${pkg}/index.js` scopes the
  context to the whole dir and is flagged again; `./locales/${lang}.js` stays
  silent.
- js-hoist-intl: drop `push` from the memo-write set — `array.push(new Intl…)`
  is unkeyed accumulation, not keyed reuse, so it's flagged again. (Bugbot)

* fix(async-await-in-loop): treat return-inside-switch as a loop early exit

loopBodyHasEarlyExit pruned whole switch subtrees so a `return` inside a switch
(which still exits the loop) was missed, flagging order-dependent first-success
loops. Split the walk: `return` prunes only nested functions; `break` still
prunes nested loops/switches that capture it. (Bugbot)

* fix(no-json-parse-stringify-clone): walk all enclosing functions for snapshot exemption

isInsideSnapshotHelper stopped at the innermost function, so a deep clone inside
a nested helper within a snapshot* function was wrongly flagged despite the
persistence exemption. Walk outward through every enclosing function. (Bugbot)

* fix(js-min-max-loop): direction-aware Math.min/max rewrite hint

The comparator check accepted both ascending (a-b) and descending (b-a) sorts
but always mapped [0]->min / [length-1]->max, inverting the suggestion for a
descending sort. Return the sort direction and pick min/max from direction +
index. (Bugbot)

* fix(performance): fix RDE-caught FPs (cast receiver, replacer) + dedup rule helpers

Two false positives surfaced by a 500-repo RDE eval (react-doctor caching
disabled):

- js-index-maps: `(x as T).find((i) => i.id === k)` on a loop-variant
  receiver behind a TS cast slipped past isLoopVariantReceiver because
  getRootIdentifierName didn't peel TSAsExpression. It now peels TS cast /
  non-null / paren wrappers (and ChainExpression) via stripParenExpression.
- no-json-parse-stringify-clone: an inline function/array replacer
  transforms the output, so structuredClone is not an equivalent rewrite —
  those are now exempt (an identifier replacer still flags, preserving the
  existing contract test).

Also consolidates duplicated guard helpers introduced across the perf rules
onto existing shared utils — isFunctionLike / isInlineFunctionExpression,
isEverySpecifierInlineType (promoted from rn-no-deep-imports into
is-type-only-import), LOOP_TYPES, ITERATOR_PRODUCING_METHOD_NAMES — removes
an unnecessary `as` cast, and trims over-long comments to match neighbor
convention.

Regression tests added for both FPs; full plugin suite green (7367 pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(changeset): patch changeset for perf-rules FP hardening

Covers the perf / js-performance / bundle-size false-positive fixes in this
PR (no changeset existed for the batch). Patch bump for
oxlint-plugin-react-doctor; the fixed group cascades the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(harness): dispatch `${type}:exit` post-order and parse fixtures without ParenthesizedExpression

Matches what oxlint compiles at runtime: every visitor gets enter/exit
pairs (Program:exit special case removed) and `preserveParens: false`
so paren-wrapped fixtures exercise the same AST rules see in production.
Harness-parity regression tests pin the rules that depended on it.

* fix(performance): precise early-exit and cancellation-guard detection in async rules

async-await-in-loop: only an early exit conditioned on an awaited result
(enclosing if/switch, or a preceding continue-guard clause like
`if (!raw) continue; … return raw;`) marks the loop order-dependent;
labeled breaks resolve to the loop they actually exit, and awaited-arg
collection reuses collectReferenceIdentifierNames.
async-defer-await: a bare `*Ref` identifier test no longer reads as a
post-await staleness check — only an actual `.current` read counts.

* fix(performance): correlate cache-memo arms in js-hoist-intl; dynamic flags in js-hoist-regexp

js-hoist-intl: isInsideCacheMemo requires the `new Intl.*` to correlate
with an actual cache read or write (lookup-initialized declarator,
??/|| with a lookup operand, ternary on a lookup test, if-guard that
writes the cache, direct `.set`) instead of any enclosing memo-shaped
expression. js-hoist-regexp: `new RegExp(pattern, flags)` with a
non-constant flags argument is not loop-invariant, so don't flag it.

* fix(performance): harden membership and iteration rules against RDE-caught FPs

js-index-maps: skip loop-bound computed-index receivers; js-set-map-lookups:
recognize single-character `includes` and indexOf-as-membership shapes
(±1/0 comparisons behind wrappers); js-combine-iterations: exempt
identity/Boolean `.filter` adjacencies (block-body identity included);
js-min-max-loop: strip parens around comparator bodies so direction
detection survives `(a, b) => (a - b)`; js-length-check-first: dominating
length-guard detection handles ||-chains, inequality operators, and
array reassignment between guard and comparison.

* fix(performance): tighten cache and clone guard precision

js-cache-property-access: written member-chain prefixes of any depth
invalidate caching reads, `.length` reads are never cache-worthy, and
shadowing parameters don't suppress outer-chain reports;
js-cache-storage: inline iteration callbacks tally storage reads into
the enclosing function instead of a fresh scope; js-tosorted-immutable:
only a fresh allocation whose binding has no other reference exempts
the `[...x].sort()` spread copy; no-json-parse-stringify-clone: the
nearest named ancestor decides the snapshot exemption (React component
names don't count) and inline revivers are exempt like replacers.

* fix(performance): reduce render-rule FPs (memo comparators, hydration timing, memo-before-return)

no-inline-prop-on-memo-component: only a default-equivalent custom
comparator keeps the memo bailout relevant; no-usememo-simple-expression:
paren-wrapped bodies and hook-call initializers are classified like their
unwrapped forms; rendering-hydration-mismatch-time: time/random only
counts when the enclosing function executes during render and test-like
files are skipped; rerender-derived-state-from-hook: transitive consumer
tracking so derivations feeding memoized values aren't flagged;
rerender-memo-before-early-return: early returns that use ANY memoized
value (including via parens/JSX) don't count as wasted memo work.

* chore(changeset): note single-reference constraint on the tosorted spread exemption

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FP-FIX(architecture): eliminate false positives across architecture/correctness/design rules (#996)

* fix(architecture): eliminate false positives across architecture/correctness/design rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* fix(no-many-boolean-props): exclude props.* wired as JSX event handlers

The props-object path only skipped invoked callbacks (props.showMenu()); a
boolean-prefixed prop wired as onClick={props.showMenu} was still counted,
unlike the destructured path. Mirror the event-handler exclusion (Bugbot).

* fix(no-render-in-render): scope render-prop exemption to props/this.props

isRenderPropReceiver exempted any X.props.renderY() call; an unrelated object
with a .props field could hide a real inline render* call. Require this.props
(or the bare props identifier). (Bugbot)

* refactor(architecture): consolidate event-handler-attribute predicate

Deslop pass over the FP-hardening changes in this PR:
- extract the `on*` event-handler-attribute check (duplicated twice in
  no-many-boolean-props) into a shared isEventHandlerAttribute type-guard util
- move the memo(_, areEqual) pass-case test into the pass-cases describe block
- add the changeset for this PR's false-positive reductions across the
  architecture/correctness/design rules (the foundation infra commit
  intentionally deferred it downstream)

Behavior-preserving; full oxlint-plugin suite green (7375 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(no-uncontrolled-input): exempt disabled inputs (RDE false positive)

RDE validation across 500 OSS repos surfaced 3 identical false positives:
`<input value={x} disabled />` (supabase user-management's read-only email
field, ported 3 ways). React suppresses its missing-`onChange` warning for
`disabled` inputs just like `readOnly`, so a disabled value-input needs no
handler and must not be flagged.

- add `disabled` to VALUE_PARTNER_ATTRIBUTES
- add no-uncontrolled-input.regressions.test.ts (disabled exempt, onInput
  exempt, genuinely-uncontrolled still flagged)

Verified in the built bin against the real files at their scanned refs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(architecture): address bot review on FP-hardening rules

Resolves Cursor Bugbot + Devin review findings on PR #996:

- no-render-in-render: scope the render-prop parameter exemption to
  COMPONENT parameters, so a `render*` param of an ordinary nested helper
  is flagged again; exempt inline member calls on a nested prop bag
  (`props.slots.renderX()`) to match its already-exempt destructured form.
- no-nested-component-definition: scope the rendered-JSX membership test to
  the candidate's own enclosing component (via isAstDescendant), so a
  sibling component's `<Inner/>` no longer falsely flags a same-named
  call-only helper in another parent.
- no-outline-none: exclude the `focus:outline-none` / `focus:shadow-none` /
  `focus:ring-0` removal utilities from the focus-ring exemption; they strip
  focus styling rather than add a ring.
- rendering-svg-precision: move MIN_DISTINCT_OVERPRECISE_SVG_TOKENS into
  constants/thresholds.ts per the magic-number convention.
- extract a shared isComponentFunction util, deduped from the prop-stack
  tracker's private copy and reused by no-render-in-render.
- drop the misplaced no-derived-useState "re-seeded draft buffer" test: it
  asserts an FP fix implemented in the state-and-effects PR (#990), not this
  architecture PR, so it failed here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(architecture): scope-aware props resolution (bugbot re-review)

Addresses two Cursor Bugbot findings from the re-review:

- no-render-in-render: the `props` carve-out no longer matches by NAME. A
  local variable named `props` (`const props = { renderRow }`) is no longer
  treated as the component's props bag, so `props.renderRow()` on it is
  flagged again. Both `rootsInProps` and `tracesToPropOrParameter` now resolve
  the root through scope via `isComponentParameterSymbol`.
- no-polymorphic-children: a body alias of the props
  (`const { children } = props`) is flagged again — it's the same smell as
  `({ children })` / `typeof props.children`. Resolution is component-aware, so
  `const { children } = node` (a non-prop parameter of a non-component tree
  walker) still stays quiet.

Extracts the shared `isComponentParameterSymbol` util (component-aware
parameter check) now used by both rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rules): dispatch :exit visitors in the test harness, count call-argument callbacks as render evidence, treat .dumi as test-noise

- run-rule.ts now dispatches `<NodeType>:exit` handlers like production
  oxlint, so stack-based rules pop between sibling declarations in tests.
- functionContainsReactRenderOutput no longer treats function expressions
  passed directly as call arguments (`.map(item => <li/>)`, `useMemo(() =>
  <div/>)`) as nested-render boundaries.
- isTestlikeFilename marks `/.dumi/` docs-site paths as non-production.

* fix(architecture): resolve fp-review findings across five architecture rules

- no-many-boolean-props: callback exclusions are scope-aware (resolve to
  the component's own props binding), match renamed destructurings on the
  VALUE name, and extend to imperative-prefixed call arguments
  (`setTimeout(props.showMenu, 100)`).
- no-nested-component-definition: nested components rendered by reference
  through a component prop (`component={Inner}`) are flagged too.
- no-render-in-render: drop the blanket this.renderX() exemption (class
  fields are per-instance, so `this.` is not a stability signal) and
  extend the render-prop carve-out to plain/defaulted/conditional aliases
  of props-rooted render props.
- prefer-module-scope-static-value: abstain when every reference is a
  read-only scalar lookup (`KEYS.includes(k)`); impure bare-call names
  only match unresolved globals or imports, not local helpers.
- react-compiler-no-manual-memoization: a nullish memo comparator
  (`memo(Inner, undefined)`) is still redundant.

* fix(correctness): resolve fp-review findings across correctness rules

- html-no-invalid-paragraph-child / html-no-nested-interactive: the
  explicit `children` prop is a real DOM child, so it no longer stops
  the ancestor walk at the attribute boundary.
- no-prevent-default: an anchor stays flagged unless the handler carries
  positive navigation evidence (router/window calls, navigate-shaped
  names, prop-handler delegation) — analytics-only handlers are still
  dead links; tag the rule test-noise for demo/test files.
- no-uncontrolled-input: a literal `disabled={false}` no longer excuses
  a missing onChange; tag the rule test-noise.
- rendering-svg-precision: count over-precise token OCCURRENCES rather
  than distinct tokens (Inkscape uniform-scale matrices repeat one
  factor), tag the rule test-noise, and add regression coverage.

* fix(design): resolve fp-review findings across design rules

- no-gray-on-colored-background: variant scopes are order-insensitive and
  !important-aware; base utilities pair with variant-scoped counterparts
  when the variant carries no same-property override; include the 950
  gray shade.
- no-layout-transition-inline: match an exact layout-property token set
  (adds border-*-width, line-height, column-width) so lookalikes like
  stroke-width stay silent.
- no-long-transition-duration: the infinite exemption is per shorthand
  segment (an animation NAME containing 'infinite' still counts) and
  decorative aria-hidden elements are exempt.
- no-outline-none: only utilities that ADD a visible ring on the
  element's OWN focus count as a replacement indicator; conditional
  tabIndex qualifies only when both branches are negative.
- no-side-tab-border: the side-scoped border color decides before the
  base color; parse-color-to-rgb learns hsl() and Tailwind underscore
  spacing.

* chore(changeset): sync fp-fix changeset with final rule behavior

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FP-FIX(security): eliminate false positives across security + security-scan rules (#993)

* fix(security): eliminate false positives across security + security-scan rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* refactor(security): deslop command-exec, metadata-secret, postmessage rules

Behavior-preserving cleanup of the FP-hardening detectors:
- command-execution-input-risk: split the two-branch `(?:…)|(?:…)` alternation
  into scanByPattern's array-of-patterns form (the documented idiom, matching
  raw-sql-injection-risk) so each taint model reads as a discrete pattern.
- package-metadata-secret: drop the fragile regex `.source` string-equality
  filter for `!pattern.test("service_role")` — robust to a re-spelling or
  flag change of the source pattern in security.ts.
- postmessage-origin-risk: inline a single-use local.

No behavior change: matching is byte-identical (281 security + security-scan
tests pass; typecheck/lint/format clean). No changeset — behavior-preserving,
and this split branch ships no changeset by design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(security): catch leaked JWT credentials in package metadata

Dropping the bare service_role keyword from package-metadata-secret must
not drop the credential itself: add JWT_LITERAL_VALUE_PATTERN (header AND
payload segments are base64url JSON, so both start with eyJ) and append it
to PACKAGE_METADATA_VALUE_PATTERNS only — in source files anon-key JWTs
are legitimate, but ANY JWT committed into package.json is a leak.

* fix(security): tighten spawn/shell taint detection in command-execution-input-risk

Recompose the patterns from named String.raw sources and split the spawn
branch into three precise shapes: tainted first argument, fixed shell
binary running tainted input via its command flag, and shell:true with
request taint anywhere in the call (order-independent). Zero-taint
spawn/exec with shell:true and a fixed argv no longer fires; the python
shell=True kwarg keeps firing on the exec branch.

* fix(security): walk every concat operand in raw-sql-injection-risk

Replace the single-operand escaper exemption with a safe-tail regex:
after the leading literal, zero or more safe operands (string literal or
SQL escaper call) followed by an unsafe one fires. One escaped operand
can no longer launder a later raw concatenation, and HTML escapers like
_.escape()/validator.escape() are no longer treated as SQL-safe;
escapeLiteral/escapeIdentifier and multi-line/parenthesized escaper
operands stay exempt.

* fix(security): cover ancestor and arrow-prop filter shapes in svg-filter-clickjacking-risk

Three alternatives: the same-tag window now tolerates arrow props
(=> consumed as a unit, bare > still bounds the tag), an unquoted
filter:url(# before the iframe catches wrapper/ancestor filters, and
the <fe…> primitive window widens to 700 chars. The mined sibling-after-
the-iframe decorative-filter FP stays silent.

* fix(security): catch concat-built role params and string-safe firebase windows

clickjacking-redirect-risk: move [?&](?:amp;)?role= outside the \b group
so concat-built and entity-encoded role URLs match (a quote before ? has
no word boundary); the ARIA role FP guard stays silent.
firebase-client-owned-authz-field: make the statement window quoted-
string-aware so a ; inside a string literal in the write's own args no
longer truncates it before the authz field.

* fix(security): treat #fragment credentials as credentialed URLs in no-secrets-in-client-code

Widen the CREDENTIALED_URL_PATTERN delimiter class [?&] -> [?&#] so a
fragment-carried credential (OAuth implicit-flow #access_token=) defeats
the public-URL exemption and still flags.

* fix(secret-in-fallback): only exempt env names public by construction

A PUBLIC/PUBLISHABLE/ANON segment anywhere in the name no longer skips
the rule: the exemption now requires a leading framework public prefix
(NEXT_PUBLIC_/EXPO_PUBLIC_/GATSBY_PUBLIC_/NUXT_PUBLIC_/REACT_APP_PUBLIC_/
VITE_PUBLIC_/bare PUBLIC_) or a publishable/anon key ending
(...PUBLISHABLE_KEY/...ANON_KEY), and never applies to names ending in
_SECRET/_PRIVATE_KEY/_PASSWORD/_PASSWD. INTERNAL_PUBLIC_WEBHOOK_SECRET
with a hardcoded fallback flags again; NEXT_PUBLIC_*, STRIPE_PUBLISHABLE_KEY,
and SUPABASE_ANON_KEY fallbacks stay silent.

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FP-FIX(react-native): eliminate false positives across react-native rules (#992)

* fix(react-native): eliminate false positives across react-native rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* fix(react-native): inline type imports + literal-flip scroll latch

- rn-no-legacy-expo-packages / rn-no-non-native-navigator: skip fully
  inline-type imports (`import { type Foo }`), not just `import type`, via the
  shared isTypeOnlyImport helper — they erase to no runtime code. Mixed
  type+value imports still flag.
- rn-no-scroll-state: a set-once latch must write a literal constant; a guard
  that reads the same state but writes a changing value
  (`if (offset !== last) setLast(offset)`) is a per-frame sync and stays
  reported. (Bugbot)

* fix(rn-no-falsy-and-render): scope-prune nested useState shadowing

isBooleanUseStateName walked the whole enclosing scope, so a nested component's
useState(false) of the same name could mask an outer useState(0) gate and hide
a real bare-0 render crash. Don't descend into nested function scopes. (Bugbot)

* refactor(react-native): consolidate type-only-import checks, reuse isHookCall

Route all six react-native import rules (bottom-sheet, deprecated-modules,
prefer-expo-image, prefer-pressable, prefer-reanimated, no-panresponder)
through the shared isTypeOnlyImport helper instead of re-implementing the
importKind / every-specifier check inline, and delegate rn-no-falsy-and-render's
isUseStateCall to the existing isHookCall. Drop the seven near-duplicate
"type-only imports are erased" comments now that the helper's doc is the single
source of truth.

Behavior-preserving: verified byte-identical over adversarial fixtures and the
283 react-native rule tests are unchanged. Net -39 LOC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rn-list-data-mapped): flag aliased recycler imports

The rule gated on `REACT_NATIVE_LIST_COMPONENTS.has(localName)`, so an
aliased recycler (`import { FlashList as FL } from "@shopify/flash-list"`)
resolved to `FL`, missed the set, and returned before the provenance check
ever ran — a false negative its two sibling list rules already handled.

Extract the recycler-resolution pattern (duplicated across all three list
rules) into a shared `resolveImportedRecyclerName` helper that maps a local
JSX name back to its canonical export via a real import, covering aliases.
rn-list-data-mapped now resolves recyclers by provenance and restricts the
built-in branch to the true built-in lists (FlatList/SectionList/
VirtualizedList), so an ambient un-imported recycler still never fires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(utils): binding-provenance helpers + inline-type-import superset

* fix(react-native): resolve real list and Dimensions bindings through aliases, namespaces, and requires

* fix(react-native): tighten latch, static-value, and list-item detectors

* fix(react-native): recognize namespace member aliases as RN bindings

`const FlatList = RN.FlatList` / `const Dimensions = ReactNative.Dimensions`
(where RN is a react-native namespace import) were misclassified as local
rebindings, silencing rn-list-data-mapped and rn-no-dimensions-get. Extract
the shared provenance check into getInitializerModuleSource — require call
(member-unwrapped) or namespace-import-rooted reference — and route both
rules through it.

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FP-FIX(frameworks): eliminate false positives across nextjs/server/tanstack/jotai/preact rules (#995)

* fix(frameworks): eliminate false positives across nextjs/server/tanstack/jotai/preact rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* fix(redirect-in-try-catch): require a real re-throw of the caught error

A catch only forwards a Next.js/TanStack redirect's control-flow error when it
re-throws the CAUGHT binding (throw e). Previously:
- nextjs: any throw in the catch suppressed (so throw new Error() — which
  swallows the redirect — was missed).
- tanstack-start: a bare isRedirect()/isNotFound() call suppressed even with no
  rethrow.
Both now require throw <caughtBinding>; fresh throws / guard-only catches flag. (Bugbot)

* refactor(rules): consolidate duplicated framework-rule helpers into shared utils

The false-positive hardening pass copy-pasted the same helpers across many
rules. Extract four shared utils — find-enclosing-function,
get-function-binding-name, collect-handler-referenced-names,
catch-clause-rethrows-caught — plus a HOOK_NAME_PATTERN constant, and repoint
every rule at them. Also reuse existing utils (isFunctionLike, getCalleeName,
findJsxAttribute) in place of re-implemented inline copies, and drop a nested
ternary.

Behavior-preserving: the full plugin suite is unchanged (7409 passing, 88
skipped); typecheck, lint, and format are clean. Net ~-250 source lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rules): close two FP patterns found by a 500-repo eval run

RDE validation (500 distinct OSS repos, cache disabled, 161 hits manually
classified) surfaced two systematic false positives:

- server-hoist-static-io flagged reads whose path derives from a handler
  param through intermediate bindings (params -> pathArray -> filePath ->
  fullPath). Taint now propagates through variable declarations, mirroring
  tanstack-start-loader-parallel-fetch.
- query-mutation-missing-invalidation missed tRPC-style invalidation
  (`utils.posts.invalidate()`). `invalidate` joins
  QUERY_CACHE_UPDATE_METHODS.

Adds a changeset for the branch's framework-rule FP hardening.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(query-destructure-result): only real forwarding suppresses, not any mention

Bugbot: isForwardedBinding treated ANY non-member-access reference as
"forwarded", so an effect dependency array ([query]) or a stray mention in
a nested closure silenced the rule while the component read query.data
field-by-field in render. Forwarding is now a positive list matching the
escape's documented intent: returned (incl. implicit return and returned
tuple/object), call argument, JSX, spread, or re-bound. A bare dependency-
array mention no longer suppresses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(tanstack-start-redirect-in-try-catch): try/finally and finally-clause throws are not swallowed

Bugbot: findEnclosingTryForThrow treated ANY enclosing TryStatement as
swallowing. A `throw redirect()` in a bare try/finally (no catch) or inside
a finally clause propagates normally, so neither must be flagged.

The nextjs sibling already had the correct walk (try BLOCK contains the
node + a catch handler exists, keep climbing past catch/finally so an
outer swallowing try is still found) — extract it as the shared
findGuardingTryStatement util and use it in both rules. Also fixes the
inner-catch false negative: a throw in an inner catch swallowed by an
OUTER try/catch is now reported.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(rules): shared helpers follow effect-invoked functions and transparent try/catch

* fix(nextjs): resolve real next/navigation bindings and effect-run control flow before flagging

* fix(server): only flag module state that is actually mutated and follow request-path bindings

* fix(tanstack-query): verify query/mutation receiver bindings through scope analysis

* fix(tanstack-start): follow loader/server-fn control flow and validator shapes before flagging

* fix(jotai): scope-aware atom and query-envelope detection in select-atom and raw-query-atom

* fix(preact-no-children-length): require component evidence before treating children as VNodes

* fix(no-document-start-view-transition): only fire in files importing React's ViewTransition

* fix(client-passive-event-listeners): trace assigned and member-expression handlers for preventDefault

* docs(changeset): describe the full framework FP-hardening surface

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* FP-FIX(state-and-effects): eliminate false positives across state/effect rules (#990)

* fix(state-and-effects): eliminate false positives across state/effect rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* fix(no-direct-state-mutation): recognize lazy useState object/array initializers

The plain-state gate only matched a direct ObjectExpression/ArrayExpression
initializer; lazy initializers (useState(() => []), useState(() => ({}))) were
excluded, so in-place mutations of that render state went unreported. Resolve
the lazy initializer's returned literal too (Bugbot).

* fix(rules): harden the shared guards behind the state/effect FP fixes

- reads-post-mount-value: member/global post-mount reads split into
  reusable predicates; wider imperative-source coverage
- contains-non-deterministic-source: don't descend into stored callbacks
- is-result-discarded-call: discarded compound positions (guards,
  statement ternaries, sequences, concise arrow bodies)
- is-namespaced-api-call: match the base receiver identifier on member
  chains (destructured router.replace(...) et al)
- is-testlike-filename: match dot-directory tooling surfaces (/.dumi/,
  /.storybook/) against the full path, before the source-root cut
- data-sink-method-names: split STRING_READ_METHOD_NAMES out so callers
  can special-case props-object string-read collisions
- new is-controlled-prop-mirror: setter wired into a JSX event-handler
  attribute + bare-prop re-sync (the controlled/uncontrolled mirror)

* fix(state-and-effects): stop flagging data sinks and discarded reads in the pass-to-parent rules

no-pass-data-to-parent: a namespaced API receiver (router.replace(...))
is not a parent callback, but a string-read name called directly ON the
props object (props.search(results)) is — un-exempt that collision.
no-pass-live-state-to-parent: recognize the widened discarded-result
shapes so guarded/ternary/concise-arrow notifications still count as
fire-and-forget hand-backs.

* fix(state-and-effects): skip controlled-prop mirrors and post-mount reads in the derived-state family

A useState seeded from a prop whose setter is wired into a JSX event
handler is a controlled/uncontrolled value mirror holding the user's
live edits — not a render-derivable value (no-derived-state,
no-derived-state-effect, no-derived-use-state). A value measured off a
ref or read from a browser global after mount can't be computed during
render, so effects that adjust state from it stay silent
(no-adjust-state-on-prop-change).

* fix(no-cascading-set-state): walk stored handlers, model early-return guard exclusivity

Inverted the function-walk skip: only INLINE callbacks handed to
non-sync-iteration callees (subscribe/then/setTimeout) are deferred;
variable-stored helpers and listener handlers are walked again — their
call sites are the writes the reducer recommendation targets (cookiekit
bench). Statement sequences now treat an early-returning guard branch as
mutually exclusive with the post-guard body, and switch runs sum
fall-through cases.

* fix(state-and-effects): suppress externally-driven state in the effect-origin rules

State written exclusively by a timer / listener / observer /
subscription (or seeded from a post-mount read) has no React handler to
fold into and no render-time value to derive, so no-initialize-state,
no-event-handler, no-prop-callback-in-effect, and no-chain-state-updates
stay silent for it while keeping handler-driven state flagged.

* fix(state-and-effects): rerender-rule FP hardening

rerender-functional-setstate: spread-merge setters are flagged only in
deferred-execution contexts (setTimeout / .then / listeners / debounce)
where the closure outlives later renders; sync render-path handlers
close over fresh state. Arithmetic reads in sync click handlers still
fire (batched double-clicks lose updates). rerender-lazy-state-init:
skip initializers that read post-mount values. rerender-state-only-in-
handlers: drop refs driven exclusively by imperative browser events
while keeping the other tested props/state reported.

* fix(advanced-event-handler-refs): scope-aware stable-handler resolution

Replace the name-based block scan with the scope-aware
findVariableInitializer (a prop param shadowing an outer stable binding
now resolves to the param). Treat the useEvent-family hooks and
empty-deps useMemo as stable handler identities, and exempt a
useRef-backed subscription receiver from the re-subscription bailout.

* fix(rules): mutation, selector, and uncontrolled-input FPs

no-direct-state-mutation: sharpen the plain-state boundary between
React-managed data and opaque third-party instances (method chains on
plain data stay plain). redux-useselector-inline-derivation: stop
flagging selectors whose derivation is the store's own memoized shape.
no-uncontrolled-input: skip mined testlike shapes and static inputs
that never carry user state.

* test(react-doctor): re-pin the state-rule e2e regressions to the hardened detectors

external-state-origin: the deferred-suppression case is now an inline
subscription callback (stored listener handlers are counted by design —
cookiekit bench), with a new true-positive pin for the stored-listener
fan-out. no-initialize-state-post-mount and no-uncontrolled-input pick
up the widened post-mount and testlike discriminators.

* FP-FIX(react-builtins): eliminate false positives across builtin DOM/JSX rules (#991)

* fix(react-builtins): eliminate false positives across builtin DOM/JSX rules

Domain slice of the rule false-positive hardening split off #986: rule
detector refinements + regression tests for this domain (and its
domain-local utils/constants/fixtures). No changeset (intentional).

* fix(button-has-type): mirror JSX spread bailout + quoted type-prop forward (createElement)

- createElement now bails on a spread-only / opaque props bag (`{ ...props }`,
  `createElement("button", props)`) the same way the JSX arm bails on a
  spread, since the forwarded object may supply `type` at runtime. null /
  absent props still report missing.
- bindsToDestructuredTypeProp recognizes a quoted `{ "type": kind }` key as a
  consumer forward, matching the bare-identifier key. (Bugbot)

* fix(button-has-type): report missing type for nullish createElement props

undefined / void 0 props carry no type, like an explicit null, so they must
report missing rather than be treated as an opaque (possibly type-forwarding)
props bag. (Bugbot follow-up)

* refactor(react-builtins): deslop FP-hardening without changing behavior

Simplify the recently added false-positive guards while preserving every
regression-tested behavior (plugin suite unchanged: 7357 pass / 91 skip).

- Extract the re-inlined `null | undefined` check into one shared
  `utils/is-nullish-expression.ts` and repoint `button-has-type`
  (`isNullishPropsArgument`, keeping its local `void 0` case) and
  `is-meaningful-jsx-child`, removing the triplicated logic.
- button-has-type: unscramble the comment stack so each rationale sits
  above the function it describes.
- no-unstable-nested-components: drop the redundant `reportNode` param
  from `enqueueCandidate` (every call site passed the candidate twice).
- void-dom-elements-no-children: `.some(isMeaningfulJsxChild)` — drop a
  gratuitous `as EsTreeNode` cast, the wrapper arrow, and the dead import.
- no-find-dom-node: inline the single-use `REACT_DOM_MODULE` constant.
- exhaustive-deps / dom-property-tags: trim comments that restated
  themselves or the file header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(changeset): add patch changeset for react-builtins false-positive fixes

The FP-hardening changes published rule behavior in oxlint-plugin-react-doctor
but the branch shipped without a changeset; add one so the fix is released.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(iframe-missing-sandbox): mirror the spread/opaque-props bailout in createElement

The JSX path already bails on a spread (`<iframe {...props} />`) since sandbox
can be forwarded at runtime, but the `createElement` path still reported a
missing sandbox for an opaque props bag (`createElement("iframe", props)`) and
for `createElement("iframe", { ...props })`. Mirror the JSX bailout — and the
identical handling in `button-has-type` — so those cases no longer false-positive,
while `createElement("iframe", null)` and an explicit object without a spread or
`sandbox` still report. Uses the shared `isNullishExpression` util. (Cursor Bugbot #991.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(void-dom-elements-no-children): ignore nullish positional children in createElement

RDE (500-repo sweep) surfaced `React.createElement("img", attr, null)` — the
idiomatic "no children" form — being falsely flagged: the createElement path
counted any positional arg via `arguments.slice(2).length > 0`, so a `null` /
`undefined` / `void 0` child triggered the diagnostic. Mirror the JSX path's
isMeaningfulJsxChild (and the sibling iframe createElement bailout) by filtering
nullish positional children before deciding. A real positional child still fires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(react-builtins): resolve all PR #991 review-thread findings

Bugbot:
- no-unstable-nested-components / no-call-component-as-function: the
  instantiation gates are now keyed by the BINDING IDENTIFIER node the
  usage resolves to (via scopes.symbolFor(...).bindingIdentifier), not by
  name, so a same-named JSX usage of a different binding (shadowed import,
  name collision) no longer counts. The binding node — not symbol.id — is
  the key because scope analysis registers a hoisted declaration under two
  symbol records that share one binding identifier. For a named function
  expression the declarator id wins over the inner `.id` (outside
  references resolve to the variable).
- no-call-component-as-function: `createElement(Name, …)` now counts as an
  instantiation alongside `<Name/>`.
- rules-of-hooks: the multi-hook render-scope escape now also covers the
  React 19 `use()` branch, so `use(...)` in a `create*` factory that
  issues several hook calls is no longer reported as non-component.

Devin:
- no-danger-with-children: the createElement path filters nullish
  positional children (`…, null)`), mirroring the JSX path.
- isNullishExpression now includes the `void` UnaryExpression case —
  every caller added it manually, and `{void 0}` renders exactly like
  `{undefined}` — deleting the three per-rule void checks and fixing the
  JSX/createElement asymmetry.
- react-native collect-text-wrapper-components: local isMeaningfulJsxChild
  renamed to isNonWhitespaceJsxChild (same name as the shared util but
  deliberately different semantics; rename only).

Regression tests added for each; plugin suite 7373 pass / 91 skip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(react-builtins): count callback JSX as render-output evidence for the enclosing function

Only name-bound function expressions (nested component definitions) are
render-evidence boundaries now; JSX inside call-arg callbacks
(rows.map(row => <tr/>)) and returned closures flows into the outer
function's render, so it counts as evidence again.

* fix(react-builtins): resolve dumi-docs mined false positives

- treat /.dumi/ doc trees as non-production files (checked against the
  full path, before the source-root slice)
- allow transform-origin on a/defs/gradients/stop SVG elements
- recognize indicator/decoration/*Children as slot props in
  jsx-no-jsx-as-prop

* fix(iframe-missing-sandbox): narrow the spread bailout to iframes without an explicit src

An explicit src marks the element as the real embed site, so a missing
sandbox alongside a spread is the author's omission and is reported
again (JSX and createElement paths). Also skip non-production files.

* fix(rules-of-hooks): tighten the render-scope escape and local non-hook callees

- gate the structural render-scope escape on factory-shaped names
  (init/create*/make*/build*) with no enclosing component or hook, so an
  arbitrary helper with two copy-pasted hooks no longer qualifies
- a use-prefixed callee resolving to a local hook-free function (e.g.
  ajv's useKeyword) is not treated as a React hook on the
  non-component report paths

* fix(no-unstable-nested-components): count escaping reads and member-expression instantiations

A nested component consumed by reference (withAnalytics(Inner),
component={Inner}, object/array/return/assignment values) escapes its
declaration site, and <Thing.Panel/> / createElement(sections.General)
instantiate member-assigned candidates — all now count as
instantiation evidence. A direct call (Inner()) still does not.

* fix(no-prevent-default): exempt href-less anchors and handlers that navigate

An <a> without href never navigates on click (anchor-as-button), and an
anchor whose handler performs its own navigation after preventDefault()
(router push, location.href assignment, window.open, platform link
openers) is custom SPA navigation — neither is a dead link. Handlers
that only log or track after preventDefault() are still flagged; the
<form> path is untouched.

* fix(exhaustive-deps): harden accessor-call dependency handling

- a computed callee (items[index]()) stays a complex dependency instead
  of collapsing to its root name and silently matching the capture
- unstable-function-dep symbol lookups resolve through the callee for
  accessor-call deps, so getConfig() finds its binding
- an unused zero-arg call dep (Date.now()) is reported as a complex
  expression instead of a callee-keyed unnecessary-dep message

* fix(react-builtins): four rule-local point fixes

- button-has-type: identifier resolution is proof only through an
  unconditional const initializer, and a destructured type key is a
  consumer forward only when the pattern roots at a function parameter
- jsx-no-comment-textnodes: a // separator line continuing a preceding
  {expression} ({used} // 512 GB) is rendered text, not a comment
- no-danger-with-children: two or more surviving children form an array
  that trips React's props.children != null guard even when every entry
  is nullish (JSX and createElement paths)
- no-call-component-as-function: a nested helper that owns hook calls
  is not exempt — calling it inlines its hooks into the caller's order

* chore(changeset): describe the fp-review hardening round

* fix(react-builtins): only count render-flowing reads as nested-component instantiation

A PascalCase escaping read now counts as instantiation evidence only
when its value flows into a render-shaped sink (JSX expression
container, createElement/cloneElement argument, return value, or a
PascalCase binding a consumer can render). Arguments to non-rendering
calls whose result is discarded (track(Inner), console.log(Inner)) no
longer resurrect an inline-only helper.

* fix(react-builtins): exclude local non-hook use* callees from the render-scope hook count

countOwnScopeHookCalls now applies the same isLocalNonHookFunctionCallee
exclusion the report paths use, so a create*-factory with one real hook
plus one local useKeyword(...) codegen-helper call no longer reaches the
render-scope threshold and wrongly exempts the real hook. A shared
visited set guards the mutual recursion between the two helpers.

* chore(react-builtins): drop unused Rule type import from rules-of-hooks

* fix(react-builtins): unify nearestEnclosingFunction callers onto findEnclosingFunction after foundation merge

* fix(no-prevent-default): keep spread anchors checked since a spread can forward a real href

* fix(no-prevent-default): require a global receiver for location.href navigation evidence

* fix(jsx-no-comment-textnodes): only treat digit-leading text after an expression as a separator

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: resolve PR #988 bugbot findings across four rules

- no-pass-live-state-to-parent: port the props.search() string-read carve-out
  from no-pass-data-to-parent (shared isWholePropsObjectReference helper)
- no-render-in-render: exempt render props called through props aliases
  (const slots = props.slots) with cycle-safe symbol tracing
- no-noninteractive-element-interactions: opaque role branches no longer
  count toward the always-interactive proof; truthy string-literal || left
  short-circuits
- effect-needs-cleanup: .listen() only counts as disposer-returning in the
  callback-argument shape (Node server.listen(port) returns the server)

---------

Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lionco#1012)

Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
…Ink TUI (millionco#979) (millionco#1017)

* fix(no-derived-state): stop flagging accumulators grown through functional updaters

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix(no-array-index-as-key): allow index keys on string-derived character arrays

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix(prefer-useReducer): require a co-update signal and correct the batching claim

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix(jsx-no-jsx-as-prop): use conditional wording when the receiver's memoization is unknown

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* chore: changeset for ink-TUI false-positive fixes

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* refactor(prefer-useReducer): reuse collectUseStateBindings for setter names

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* refactor(rules): drop unneeded casts and nested ternaries flagged by ship review

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* fix(no-array-index-as-key): follow local bindings to string-derived receivers

A 500-repo eval found the string-derived exemption missing the common
`const parts = line.split(urlRegex); parts.map(...)` shape (openreplay,
outline) — the receiver is an identifier, not the split call itself.
Follow the binding to its initializer, bounded by the existing type-
resolution depth limit. Also dedupe the "Array.from whose first argument
is provably a string" predicate that existed twice, and correct the doc
comment: split produces positional fragments (characters, lines,
tokens), not always one entry per character.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(react-builtins): stop classifying lazy() as memoising

React.lazy defers loading but does not skip re-renders, so treating it
like memo() made jsx-no-jsx-as-prop assert a memo bailout that does not
exist (3 of 5 assertive messages in a 500-repo eval sat on lazy
receivers) and made the memoised-gated rules (jsx-no-new-object/array/
function-as-prop, prefer-stable-empty-fallback) report fresh-reference
props to components with no bailout to defeat. lazy receivers now
resolve "unknown": conditional wording, and the gated rules stay quiet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: update changeset for the eval-driven fixes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…N rules (millionco#1013)

Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
…and workflows (millionco#1015)

Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
@fengmk2 fengmk2 self-assigned this Jul 2, 2026
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

📦 GitHub Action release recommended

This PR changes the React Doctor GitHub Action's release surface:

  • action.yml

The composite action is versioned independently from the npm packages, so it
needs its own git tag once this merges. Based on the PR title, this looks like
a patch bump (fix / refactor / chore / docs): v0.0.0v0.0.1.

After merging, cut the tag from the merge commit on main (tags are GPG-signed
annotated tags, so run this locally where your signing key is configured):

git checkout main && git pull --ff-only
merge_commit=$(git rev-parse HEAD)
git tag -a v0.0.1 "$merge_commit" -m "react-doctor action v0.0.1"
git tag -fa v0 "$merge_commit" -m "react-doctor action v0 (floating major -> v0.0.1)"
git push origin v0.0.1
git push --force origin v0   # moves only the floating major pointer

This bump can also be performed automatically on merge — set the repo
variable AUTO_BUMP_ACTION_TAG=true. Recommendation by the Action Version Bump workflow.

@pkg-pr-new

pkg-pr-new Bot commented Jul 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/eslint-plugin-react-doctor@5
npm i https://pkg.pr.new/oxlint-plugin-react-doctor@5
npm i https://pkg.pr.new/react-doctor@5

commit: 609f302

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants