From 6fd746d3bd7b7625e40325483f2779b79059dcee Mon Sep 17 00:00:00 2001 From: Danijel Martinek Date: Fri, 10 Jul 2026 16:27:16 +0200 Subject: [PATCH] fix(core-eslint): activate React rules-of-hooks for all TSX surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/web-tanstack/src/routes/__root.tsx | 22 ++++++++++++---------- packages/core-eslint/base.js | 8 ++++++++ packages/core-eslint/package.json | 1 + pnpm-lock.yaml | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/apps/web-tanstack/src/routes/__root.tsx b/apps/web-tanstack/src/routes/__root.tsx index 25cde5e..28e1f83 100644 --- a/apps/web-tanstack/src/routes/__root.tsx +++ b/apps/web-tanstack/src/routes/__root.tsx @@ -1,6 +1,17 @@ import { Outlet, createRootRoute } from "@tanstack/react-router"; 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 */} + + + + ); +} + export const Route = createRootRoute({ loader: async () => { try { @@ -12,14 +23,5 @@ export const Route = createRootRoute({ return { nonce: "" }; } }, - component: () => { - const { nonce } = Route.useLoaderData(); - return ( - <> - {/* nonce exposed to client so instrumentation-client.ts can read it */} - - - - ); - }, + component: RootComponent, }); diff --git a/packages/core-eslint/base.js b/packages/core-eslint/base.js index 90ed3ef..c9122ee 100644 --- a/packages/core-eslint/base.js +++ b/packages/core-eslint/base.js @@ -3,6 +3,7 @@ import eslintConfigPrettier from "eslint-config-prettier"; import tseslint from "typescript-eslint"; import turboPlugin from "eslint-plugin-turbo"; import boundaries from "eslint-plugin-boundaries"; +import reactHooks from "eslint-plugin-react-hooks"; import globals from "globals"; import conformancePlugin from "./plugin.js"; import path from "node:path"; @@ -195,6 +196,13 @@ export default [ "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 // consumer feature's bind-production / bind-dev-seed (spec ยง 2.2 Rule E1). // J โ€” Direct `payload.jobs.*` access is forbidden outside the integration diff --git a/packages/core-eslint/package.json b/packages/core-eslint/package.json index 5b4190a..fe578e0 100644 --- a/packages/core-eslint/package.json +++ b/packages/core-eslint/package.json @@ -19,6 +19,7 @@ "eslint": "^9.20.0", "eslint-config-prettier": "^10.1.0", "eslint-plugin-boundaries": "^4.2.2", + "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-turbo": "^2.4.0", "typescript-eslint": "^8.25.0", "vitest": "^3.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e62c51..08312c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -733,6 +733,9 @@ importers: eslint-plugin-boundaries: 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)) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-turbo: specifier: ^2.4.0 version: 2.9.4(eslint@9.39.4(jiti@2.7.0))(turbo@2.9.4) @@ -10680,6 +10683,15 @@ packages: peerDependencies: 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: resolution: { @@ -24134,6 +24146,10 @@ snapshots: - eslint-import-resolver-webpack - 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): dependencies: dotenv: 16.0.3