Skip to content

React Compiler diagnostics are bucketed as hooks, so compiler-readiness is always empty #89

Description

@serge-ivo

Residue from #29 (the React analyzer itself shipped in 75d40d6; this is the one bucket that never fills).

Problem

The React Health page has a Compiler Readiness section whose blurb promises "React Compiler diagnostics reported by the official React lint rules" (app/src/monitor/views/reactHealth.logic.ts:85-89). On a repo that has the current eslint-plugin-react-hooks configured and is reporting compiler diagnostics, that section reads 0 issues. The findings are all filed under Hooks instead, so the page cannot answer the question it advertises: is this codebase ready for the React Compiler?

Where it is

src/runners/react.ts:55-66:

function categoryForRule(rule: string | undefined): ReactCategoryId {
	if (!rule) return "component-structure";
	if (rule === "react-hooks/exhaustive-deps") return "effects";
	if (rule.startsWith("react-hooks/")) return "hooks";      // :58 — swallows everything
	if (rule.startsWith("react-refresh/")) return "fast-refresh";
	if (rule.startsWith("jsx-a11y/")) return "accessibility";
	if (rule === "react/jsx-key" || /(^|\/)(jsx-key|no-array-index-key|key)/.test(rule)) return "rendering";
	if (/compiler/i.test(rule)) return "compiler-readiness";  // :62 — unreachable for these
	...

The compiler test at :62 matches on the string "compiler" appearing in the rule id. No current rule id contains it.

Why :62 never fires

Verified against the installed plugin, [email protected] (app/node_modules/eslint-plugin-react-hooks). Its recommended-latest config enables 17 rules, all reported by ESLint under the react-hooks/ prefix:

react-hooks/rules-of-hooks              react-hooks/immutability
react-hooks/exhaustive-deps             react-hooks/globals
react-hooks/static-components           react-hooks/refs
react-hooks/use-memo                    react-hooks/set-state-in-effect
react-hooks/void-use-memo               react-hooks/error-boundaries
react-hooks/preserve-manual-memoization react-hooks/purity
react-hooks/incompatible-library        react-hooks/set-state-in-render
react-hooks/unsupported-syntax          react-hooks/config
react-hooks/gating

These are the React Compiler diagnostics — the plugin ships them as the compiler-powered lint rules (meta.docs.url points at react.dev/reference/eslint-plugin-react-hooks/lints/<rule>). Not one of them contains "compiler". They all match :58 first, so every one is bucketed hooks.

:62 is reachable only for the deprecated standalone eslint-plugin-react-compiler (rule id react-compiler/react-compiler), which was folded into eslint-plugin-react-hooks at v6. So on any project using the current plugin, compiler-readiness is structurally always 0.

Two smaller mis-bucketings fall out of the same line:

  • Effects. Only exhaustive-deps is routed to effects (:57). set-state-in-effect, no-deriving-state-in-effects, exhaustive-effect-dependencies and memoized-effect-dependencies are effect rules and land in hooks.
  • Error boundaries. react-hooks/error-boundaries lands in hooks, not error-boundary.

What to do

  1. Cheapest, ships alone: add an explicit react-hooks/<rule> → bucket table consulted before the :58 prefix fallback, and keep :58 as the fallback for rules the table does not know (forward-compatible with plugin releases that add rules). Proposed mapping, judgment calls flagged below:
    • compiler-readiness: immutability, purity, globals, refs, set-state-in-render, preserve-manual-memoization, incompatible-library, unsupported-syntax, config, gating
    • effects: set-state-in-effect, no-deriving-state-in-effects, exhaustive-effect-dependencies, memoized-effect-dependencies, plus the existing exhaustive-deps
    • error-boundary: error-boundaries
    • hooks: rules-of-hooks, hooks, capitalized-calls, component-hook-factories
    • component-structure: static-components
    • use-memo / void-use-memo / memo-dependencies: see open question
  2. Keep the /compiler/i test as a trailing fallback so the legacy react-compiler/react-compiler rule still lands correctly.
  3. Optional, once 1 lands: surface the detected plugin version in details.tooling, so a report can be read years later without guessing which rule set produced it.

Alternatives considered and rejected

  • Read the bucket off the rule's ESLint meta. Rejected: there is no category there. Checked purity, immutability, use-memo, set-state-in-effect, error-boundaries, rules-of-hooks — every one is { type: "problem", docs: { description, recommended, url } }. Nothing machine-readable says "this is a compiler diagnostic". A static map is the only option.
  • Just reorder :58 and :62. Rejected: it changes nothing. /compiler/i does not match any of these rule ids in either order.
  • Match on the meta.docs.url path (/lints/) to mean "compiler diagnostic". Rejected: it would require loading the target repo's node_modules at scan time, and the analyzer only ever sees ESLint's JSON output, not the plugin object.
  • Drop the compiler-readiness bucket. Rejected: the React Compiler question is exactly the differentiated thing this analyzer is for, and the app already has a section built for it.

Acceptance criteria

  • A unit test feeds parseReactEslintIssues output containing react-hooks/purity, react-hooks/immutability and react-hooks/set-state-in-render and asserts they land in compiler-readiness, not hooks.
  • A test asserts react-hooks/set-state-in-effect lands in effects and react-hooks/error-boundaries in error-boundary.
  • react-hooks/rules-of-hooks still lands in hooks; react-hooks/exhaustive-deps still lands in effects.
  • An unknown future react-hooks/whatever still lands in hooks rather than crashing or defaulting to component-structure.
  • src/runners/react.test.ts:201 (expect.objectContaining({ id: "compiler-readiness", issues: 0 })) is re-examined — it currently pins the empty bucket as expected on a fixture with no ESLint at all, which is fine, but it should no longer be the only assertion about that bucket.

Constraints

  • Version skew. Projects on eslint-plugin-react-hooks v5 only ever emit rules-of-hooks and exhaustive-deps; the table must leave those repos exactly as they are today (empty compiler-readiness is the honest answer there).
  • Lockstep with the app. app/src/monitor/views/reactHealth.logic.ts:235-247 is a verbatim copy of this same function. The app prefers the CLI's details.categories when present (:377), so fixing the CLI fixes the section counts — but buildReactFixQueue (:443) still calls the local copy, so fix-queue rows keep the wrong category label until the app is updated too. Tracked in the companion app issue linked below.

Open question for the maintainer

use-memo, void-use-memo, memo-dependencies, preserve-manual-memoization and static-components are compiler diagnostics by origin but read as memoization/structure advice to a user. Do they belong in compiler-readiness (origin) or in component-structure (what the developer would go fix)? Pick one and the table is unambiguous.

Related

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingframework-analyzerFramework/language-specific analyzer work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions