eslint.config.mjs
Rama main · réplica de lectura del repositorio original.
import nextCoreWebVitals from 'eslint-config-next/core-web-vitals';
import nextTypescript from 'eslint-config-next/typescript';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import i18next from 'eslint-plugin-i18next';
import i18nextHtmlEntities from 'eslint-plugin-i18next/lib/options/htmlEntities.js';
// ESLint 9 flat config. eslint-config-next 16 ships native flat configs and
// peer-requires eslint >=9, so they are spread in directly (no FlatCompat).
const eslintConfig = [
{
ignores: [
'**/node_modules/**',
'**/dist/**',
'**/.next/**',
'**/coverage/**',
'**/build/**',
'**/*.min.js',
// Generated reports and vendored/CommonJS build scripts — not app source.
'e2e/playwright-report/**',
'apps/frontend/public/**',
'**/*.cjs',
],
},
...nextCoreWebVitals,
...nextTypescript,
{
rules: {
// Repo root is not the Next.js app (App Router lives in apps/frontend),
// so this rule only emits a spurious "Pages directory cannot be found" notice.
'@next/next/no-html-link-for-pages': 'off',
// New in eslint-config-next 16.3: flags every location.assign/href to a
// relative destination assuming an internal Next page. This app navigates
// by full-page load DELIBERATELY for backend endpoints (/auth/logout) and
// post-auth state resets — 17 sites, all intentional hard navigations.
// The rule's assumption doesn't hold for this codebase.
'@next/next/no-location-assign-relative-destination': 'off',
'react/no-unescaped-entities': 'off',
// The following rules are globally disabled because the existing codebase
// has a large volume of occurrences and fixing them safely is tracked
// debt. They should be re-enabled incrementally per-area rather than in
// one giant PR. See remediation runbooks L-01..L-08 and T-13.
'@typescript-eslint/no-explicit-any': 'off', // BLOCKED: ~7,167 occurrences across 999 files — unsafe mass refactor
'@typescript-eslint/no-unused-vars': 'off', // BLOCKED: ~1,106 occurrences across 451 files — many intentional destructurings
'react/display-name': 'error', // re-enabled TD-01c
'@typescript-eslint/ban-ts-comment': 'off', // BLOCKED: ~189 ts-ignore comments across 76 files — needs per-site verification
'@typescript-eslint/no-empty-object-type': 'error', // re-enabled TD-01e
'@typescript-eslint/prefer-as-const': 'error', // already enabled TD-01f
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off', // BLOCKED: ~98 occurrences across 22 files incl. core services/provider adapters
// React Compiler rule: 108 occurrences across 42 frontend files. Each fix
// requires careful dependency review to avoid breaking memoization semantics.
// Tracked debt (L-02 / F-07); downgrade to warn so CI stays green while the
// debt remains visible.
'react-hooks/preserve-manual-memoization': 'warn', // BLOCKED: ~108 occurrences across 42 files — careful memoization review required
},
},
{
// Accessibility rules for the Next.js frontend. eslint-config-next already
// registers the jsx-a11y plugin, so only its recommended ruleset is turned
// on here (scoped to the frontend source).
files: ['apps/frontend/src/**/*.{js,jsx,ts,tsx}'],
rules: {
...jsxA11y.configs.recommended.rules,
// High-volume rules that can't be safely auto-fixed are tracked as
// warnings (visible, non-blocking) instead of being silently disabled:
// - the click-on-<div> pattern is pervasive and each fix needs a real
// keyboard handler / element change (tracked debt);
// - <video>/<audio> show user-generated media for which no caption
// track exists;
// - autoFocus is used intentionally on a handful of modals/inputs;
// - most flagged <label>s sit beside custom widgets (role="switch"
// buttons, comboboxes, button grids) with no labelable form control,
// so a correct fix is per-case (aria-labelledby) rather than auto.
'jsx-a11y/no-static-element-interactions': 'warn',
'jsx-a11y/click-events-have-key-events': 'warn',
'jsx-a11y/media-has-caption': 'warn',
'jsx-a11y/no-autofocus': 'warn',
'jsx-a11y/label-has-associated-control': 'warn',
// The Designer canvas + video timeline are keyboard-interactive editor
// surfaces (a focusable container with key handlers); jsx-a11y 6.7.1 has
// no accepted role for that shape, so these two are tracked as warnings.
'jsx-a11y/no-noninteractive-element-interactions': 'warn',
'jsx-a11y/no-noninteractive-tabindex': 'warn',
// Native browser dialogs are banned (agents/ui-standards.md hard rule 6):
// they block the event loop, ignore the theme tokens, can't be translated
// through useT(), can't be focus-trapped alongside the app's stacked
// modals, and are silently auto-dismissed by Playwright in e2e/.
// Use useDecisionModal()/areYouSure() to confirm, usePromptModal() to
// collect text, and useToaster().show(…) to report.
//
// no-alert is the rule that does the real work here — it is the only one
// of the two that catches the `window.`-qualified forms. no-restricted-
// globals matches bare `alert(`/`confirm(`/`prompt(` only, and is kept
// purely for its more actionable messages.
'no-alert': 'error',
'no-restricted-globals': [
'error',
{
name: 'alert',
message:
'Use useToaster().show(…), or useDecisionModal().open({ onlyApprove: true }) when it must block.',
},
{
name: 'confirm',
message: 'Use useDecisionModal() / areYouSure().',
},
{
name: 'prompt',
message: 'Use usePromptModal().',
},
],
},
},
{
// i18n literal-string audit for the Next.js frontend (dev/I18N_UPDATE.md §S0/§G1).
// OPT-IN: enabled only with I18N_LINT=1 (e.g. `I18N_LINT=1 pnpm exec eslint apps/frontend/src`).
// Off by default because the residual warnings are dominated by known false positives
// (label fallbacks on internally-translated components), and CI's eslint.yml uploads
// SARIF to GitHub code scanning — a warn-level default floods it with ~13k alerts and
// fails the code-scanning PR gate. Promote to always-on 'error' (full G1) only after
// the false-positive allowlist is complete.
files: ['apps/frontend/src/**/*.{js,jsx,ts,tsx}'],
plugins: { i18next },
rules: {
'i18next/no-literal-string': [
process.env.I18N_LINT ? 'warn' : 'off',
{
// Catch JSX text, JSX attributes, and non-JSX literals (e.g. hook
// throws, option labels) so the report covers the whole UI surface.
mode: 'all',
message: 'literal string should be translated (i18n)',
'jsx-attributes': {
exclude: [
// Defaults provided by the plugin.
'className',
'styleName',
'style',
'type',
'key',
'id',
'width',
'height',
// Non-user-facing / technical identifiers.
'name',
'data-.*',
'src',
'href',
'to',
'target',
'rel',
'role',
// Bespoke form primitives translate internally: `translationKey` is
// the i18n key; `label` is its English fallback passed alongside.
'translationKey',
'autoComplete',
'autoCapitalize',
// Next.js <Script> and similar enum props.
'strategy',
// Color values (hex / named colors) are design tokens, not copy.
'color',
// fetch/CopilotKit credential mode is a technical enum.
'credentials',
// aria-* is technical except aria-label, which is user-facing.
'aria-(?!label).*',
],
},
callees: {
exclude: [
// Defaults provided by the plugin.
'i18n(ext)?',
't',
'require',
// react-hook-form field registration — the string is a field name.
'register',
'.*\\.register',
'addEventListener',
'removeEventListener',
'postMessage',
'getElementById',
'dispatch',
'commit',
'includes',
'indexOf',
'endsWith',
'startsWith',
// Translation helpers used in this codebase.
'i18n\\.t',
'i18next\\.t',
'getT',
// Logging is never user-facing UI copy.
'console.*',
'Logger.*',
// DOM APIs whose string arguments are technical identifiers.
'setAttribute',
'removeAttribute',
'querySelector',
'querySelectorAll',
'classList\\..*',
'window\\.open',
// Network / cookie APIs with technical string args.
'fetch',
'useCookie',
'getCookie',
'setCookie',
'mutate',
'searchParams\\..*',
// Analytics / event APIs with technical event names.
'gtag',
// Standard library / locale APIs with technical string args.
'Intl\\..*',
'countries\\.getName',
// SWR cache keys / class-name helpers are technical identifiers.
'useSWR(?:<.*>)?',
'useSWRInfinite(?:<.*>)?',
'cn',
'clsx',
'twMerge',
'classNames',
],
},
words: {
exclude: [
// Defaults provided by the plugin.
'[0-9!-/:-@[-`{-~]+',
'[A-Z_-]+',
i18nextHtmlEntities,
/^\p{Emoji}+$/u,
// Next.js directives and strict mode pragma.
'use client',
'use server',
'use strict',
],
},
},
],
},
},
];
export default eslintConfig;