fix(core-eslint): activate React rules-of-hooks for all TSX surfaces

next.js and react-internal.js are plain re-exports of base, so no
rules-of-hooks checking was active anywhere despite three React apps
and core-ui. Wire eslint-plugin-react-hooks in base.js scoped to
**/*.tsx (non-React packages untouched). Fixes the one violation it
surfaced: web-tanstack's root route called Route.useLoaderData inside
an anonymous component callback — extracted to a named RootComponent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 16:27:16 +02:00
parent 9b04fae975
commit 6fd746d3bd
4 changed files with 37 additions and 10 deletions

View File

@@ -1,6 +1,17 @@
import { Outlet, createRootRoute } from "@tanstack/react-router"; import { Outlet, createRootRoute } from "@tanstack/react-router";
import { getNonce } from "@repo/core-shared/security/tanstack"; import { getNonce } from "@repo/core-shared/security/tanstack";
function RootComponent() {
const { nonce } = Route.useLoaderData();
return (
<>
{/* nonce exposed to client so instrumentation-client.ts can read it */}
<meta name="csp-nonce" content={nonce} />
<Outlet />
</>
);
}
export const Route = createRootRoute({ export const Route = createRootRoute({
loader: async () => { loader: async () => {
try { try {
@@ -12,14 +23,5 @@ export const Route = createRootRoute({
return { nonce: "" }; return { nonce: "" };
} }
}, },
component: () => { component: RootComponent,
const { nonce } = Route.useLoaderData();
return (
<>
{/* nonce exposed to client so instrumentation-client.ts can read it */}
<meta name="csp-nonce" content={nonce} />
<Outlet />
</>
);
},
}); });

View File

@@ -3,6 +3,7 @@ import eslintConfigPrettier from "eslint-config-prettier";
import tseslint from "typescript-eslint"; import tseslint from "typescript-eslint";
import turboPlugin from "eslint-plugin-turbo"; import turboPlugin from "eslint-plugin-turbo";
import boundaries from "eslint-plugin-boundaries"; import boundaries from "eslint-plugin-boundaries";
import reactHooks from "eslint-plugin-react-hooks";
import globals from "globals"; import globals from "globals";
import conformancePlugin from "./plugin.js"; import conformancePlugin from "./plugin.js";
import path from "node:path"; import path from "node:path";
@@ -195,6 +196,13 @@ export default [
"no-restricted-imports": "off", "no-restricted-imports": "off",
}, },
}, },
// React rules-of-hooks for every TSX surface (apps, core-ui, feature
// ui/**). Scoped to .tsx so non-React packages are untouched.
{
files: ["**/*.tsx"],
plugins: { "react-hooks": reactHooks },
rules: reactHooks.configs.recommended.rules,
},
// E1 — Event handlers must not be re-exported. Wire them only inside the // E1 — Event handlers must not be re-exported. Wire them only inside the
// consumer feature's bind-production / bind-dev-seed (spec § 2.2 Rule E1). // consumer feature's bind-production / bind-dev-seed (spec § 2.2 Rule E1).
// J — Direct `payload.jobs.*` access is forbidden outside the integration // J — Direct `payload.jobs.*` access is forbidden outside the integration

View File

@@ -19,6 +19,7 @@
"eslint": "^9.20.0", "eslint": "^9.20.0",
"eslint-config-prettier": "^10.1.0", "eslint-config-prettier": "^10.1.0",
"eslint-plugin-boundaries": "^4.2.2", "eslint-plugin-boundaries": "^4.2.2",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-turbo": "^2.4.0", "eslint-plugin-turbo": "^2.4.0",
"typescript-eslint": "^8.25.0", "typescript-eslint": "^8.25.0",
"vitest": "^3.1.0" "vitest": "^3.1.0"

16
pnpm-lock.yaml generated
View File

@@ -733,6 +733,9 @@ importers:
eslint-plugin-boundaries: eslint-plugin-boundaries:
specifier: ^4.2.2 specifier: ^4.2.2
version: 4.2.2(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)) version: 4.2.2(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react-hooks:
specifier: ^5.2.0
version: 5.2.0(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-turbo: eslint-plugin-turbo:
specifier: ^2.4.0 specifier: ^2.4.0
version: 2.9.4(eslint@9.39.4(jiti@2.7.0))(turbo@2.9.4) version: 2.9.4(eslint@9.39.4(jiti@2.7.0))(turbo@2.9.4)
@@ -10680,6 +10683,15 @@ packages:
peerDependencies: peerDependencies:
eslint: ">=6.0.0" eslint: ">=6.0.0"
eslint-plugin-react-hooks@5.2.0:
resolution:
{
integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==,
}
engines: { node: ">=10" }
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-turbo@2.9.4: eslint-plugin-turbo@2.9.4:
resolution: resolution:
{ {
@@ -24134,6 +24146,10 @@ snapshots:
- eslint-import-resolver-webpack - eslint-import-resolver-webpack
- supports-color - supports-color
eslint-plugin-react-hooks@5.2.0(eslint@9.39.4(jiti@2.7.0)):
dependencies:
eslint: 9.39.4(jiti@2.7.0)
eslint-plugin-turbo@2.9.4(eslint@9.39.4(jiti@2.7.0))(turbo@2.9.4): eslint-plugin-turbo@2.9.4(eslint@9.39.4(jiti@2.7.0))(turbo@2.9.4):
dependencies: dependencies:
dotenv: 16.0.3 dotenv: 16.0.3