Files
8765f766e1 chore(lint): adopt oxlint as the linter + CI gate; fix the bugs it surfaced (#761)
ESLint was misconfigured (only globals.browser → 47 false no-undef) and run
NOWHERE in CI, so 259 errors had accumulated unnoticed. Replace it with oxlint
(Rust, ~50-100x faster) as the primary linter AND a real CI gate so lint debt
can't silently pile up again.

Tooling:
- frontend/.oxlintrc.json — correctness=error; no-unused-vars with the existing
  ^[A-Z_] convention; node/vitest env overrides + AudioWorklet/__APP_VERSION__
  globals (kills the false no-undef class); max-lines:500 (warn).
- package.json: `lint` → oxlint, `lint:fix`, `lint:hooks` (advisory eslint).
- eslint.config.js stripped to ONLY the React-Compiler rule family oxlint can't
  do yet (set-state-in-effect etc.), run via `lint:hooks`, NOT gated. Drop once
  oxlint's JS-plugin support leaves alpha.
- ci.yml: new "Frontend lint (oxlint)" step in the Tests job — the gate.

Real bugs oxlint caught (were buried in ESLint's noise):
- GlossaryPanel: <X/> close-icon used but never imported → the edit-row cancel
  button threw ReferenceError at render. Imported X.
- Two use*-named NON-hooks (useEngine action, useArchetypeAsProfile API call)
  tripped rules-of-hooks; suppressed with documented disables (renaming these
  misleading names is a worthwhile follow-up).

Cleanup to reach a 0-error gate: removed 52 genuinely-dead vars/imports across
18 files (heavy in App.jsx — stale useState left over from prior refactors) and
4 behavior-preserving autofixes (no-useless-fallback-in-spread / no-useless-escape).

Verified: oxlint 0 errors; bun install --frozen-lockfile in sync (Docker rule);
vite build passes; full suite 638/638.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 05:08:52 +05:30

39 lines
1.2 KiB
JSON

{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"env": {
"browser": true,
"es2024": true
},
"globals": {
"__APP_VERSION__": "readonly",
"AudioWorklet": "readonly",
"AudioWorkletNode": "readonly",
"AudioWorkletProcessor": "readonly",
"registerProcessor": "readonly"
},
"plugins": ["react", "import", "unicorn"],
"ignorePatterns": ["dist/", "src-tauri/", "node_modules/"],
"categories": {
"correctness": "error"
},
"rules": {
"no-unused-vars": ["error", { "varsIgnorePattern": "^[A-Z_]", "argsIgnorePattern": "^_", "caughtErrors": "none" }],
"no-empty": ["warn", { "allowEmptyCatch": true }],
"no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }],
"react/exhaustive-deps": "warn",
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }],
"max-lines": ["warn", { "max": 500, "skipBlankLines": true, "skipComments": true }]
},
"overrides": [
{
"files": ["vite.config.js", "*.config.js", "eslint.config.js"],
"env": { "node": true }
},
{
"files": ["**/*.test.{js,jsx}", "**/*.spec.{js,jsx}", "src/test/**"],
"env": { "node": true, "vitest": true }
}
]
}