Files
claudecodeui/eslint.config.js
2026-04-15 14:22:16 +03:00

231 lines
10 KiB
JavaScript

import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import { createNodeResolver, importX } from "eslint-plugin-import-x";
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
import boundaries from "eslint-plugin-boundaries";
import tailwindcss from "eslint-plugin-tailwindcss";
import unusedImports from "eslint-plugin-unused-imports";
import globals from "globals";
export default tseslint.config(
{
ignores: ["dist/**", "node_modules/**", "public/**"],
},
{
files: ["src/**/*.{ts,tsx,js,jsx}"],
extends: [js.configs.recommended, ...tseslint.configs.recommended],
plugins: {
react,
"react-hooks": reactHooks, // for following React rules such as dependencies in hooks, keys in lists, etc.
"react-refresh": reactRefresh, // for Vite HMR compatibility
"import-x": importX, // for import order/sorting. It also detercts circular dependencies and duplicate imports.
tailwindcss, // for detecting invalid Tailwind classnames and enforcing classname order
"unused-imports": unusedImports, // for detecting unused imports
},
languageOptions: {
globals: {
...globals.browser,
},
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
settings: {
react: { version: "detect" },
},
rules: {
// --- Unused imports/vars ---
"unused-imports/no-unused-imports": "warn",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
// --- React ---
"react/jsx-key": "warn",
"react/jsx-no-duplicate-props": "error",
"react/jsx-no-undef": "error",
"react/no-children-prop": "warn",
"react/no-danger-with-children": "error",
"react/no-direct-mutation-state": "error",
"react/no-unknown-property": "warn",
"react/react-in-jsx-scope": "off",
// --- React Hooks ---
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
// --- React Refresh (Vite HMR) ---
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
// --- Import ordering & hygiene ---
"import-x/no-duplicates": "warn",
"import-x/order": [
"warn",
{
groups: [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
],
"newlines-between": "always",
},
],
// --- Tailwind CSS ---
"tailwindcss/classnames-order": "warn",
"tailwindcss/no-contradicting-classname": "warn",
"tailwindcss/no-unnecessary-arbitrary-value": "warn",
// --- Disabled base rules ---
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-require-imports": "off",
"no-case-declarations": "off",
"no-control-regex": "off",
"no-useless-escape": "off",
},
},
{
files: ["server/**/*.{js,ts}"], // apply this block only to backend source files
ignores: ["server/**/*.d.ts"], // skip generated declaration files in backend linting
plugins: {
boundaries, // enforce backend architecture boundaries (module-to-module contracts)
"import-x": importX, // keep import hygiene rules (duplicates, unresolved paths, etc.)
"unused-imports": unusedImports, // remove dead imports/variables from backend files
},
languageOptions: {
parser: tseslint.parser, // parse both JS and TS syntax in backend files
parserOptions: {
ecmaVersion: "latest", // support modern ECMAScript syntax in backend code
sourceType: "module", // treat backend files as ESM modules
},
globals: {
...globals.node, // expose Node.js globals such as process, Buffer, and __dirname equivalents
},
},
settings: {
"boundaries/include": ["server/**/*.{js,ts}"], // only analyze dependency boundaries inside backend files
"import/resolver": {
// boundaries resolves imports through eslint-module-utils, which reads the classic
// import/resolver setting instead of import-x/resolver-next.
typescript: {
project: ["server/tsconfig.json"], // resolve backend aliases using the canonical backend tsconfig
alwaysTryTypes: true, // keep normal TS package/type resolution working alongside aliases
},
node: {
extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"], // preserve Node-style fallback resolution for plain files
},
},
"import-x/resolver-next": [
// ESLint's import plugin does not read tsconfig path aliases on its own.
// This resolver teaches import-x how to understand the backend-only "@/*"
// mapping defined in server/tsconfig.json, which fixes false no-unresolved errors in editors.
createTypeScriptImportResolver({
project: ["server/tsconfig.json"], // point the resolver at the canonical backend tsconfig instead of the frontend one
alwaysTryTypes: true, // keep standard TypeScript package resolution working while backend aliases are enabled
}),
// Keep Node-style resolution available for normal package imports and plain relative JS files.
// The TypeScript resolver handles aliases, while the Node resolver preserves the expected fallback behavior.
createNodeResolver({
extensions: [".mjs", ".cjs", ".js", ".json", ".node", ".ts", ".tsx"],
}),
],
"boundaries/elements": [
{
type: "backend-shared-types", // shared backend type contract that modules may consume without creating runtime coupling
pattern: ["server/shared/types.{js,ts}"], // support the current shared types path
mode: "file", // treat the types file itself as the boundary element instead of the whole folder
},
{
type: "backend-module", // logical element name used by boundaries rules below
pattern: "server/modules/*", // each direct folder in server/modules is treated as one module boundary
mode: "folder", // classify dependencies at folder-module level (not per individual file)
capture: ["moduleName"], // capture the module folder name for messages/debugging/template use
},
],
},
rules: {
// --- Unused imports/vars (backend) ---
"unused-imports/no-unused-imports": "warn", // warn when imports are not used so they can be cleaned up
"unused-imports/no-unused-vars": "off", // keep backend signal focused on dead imports instead of local unused variables
// --- Import hygiene (backend) ---
"import-x/no-duplicates": "warn", // prevent duplicate import lines from the same module
"import-x/order": [
"warn", // keep backend import grouping/order consistent with the frontend config
{
groups: [
"builtin", // Node built-ins such as fs, path, and url come first
"external", // third-party packages come after built-ins
"internal", // aliased internal imports such as @/... come next
"parent", // ../ imports come after aliased internal imports
"sibling", // ./foo imports come after parent imports
"index", // bare ./ imports stay last
],
"newlines-between": "always", // require a blank line between import groups in backend files too
},
],
"import-x/no-unresolved": "error", // fail when an import path cannot be resolved
"import-x/no-useless-path-segments": "warn", // prefer cleaner paths (remove redundant ./ and ../ segments)
"import-x/no-absolute-path": "error", // disallow absolute filesystem imports in backend files
// --- General safety/style (backend) ---
eqeqeq: ["warn", "always", { null: "ignore" }], // avoid accidental coercion while still allowing x == null checks
// --- Architecture boundaries (backend modules) ---
"boundaries/dependencies": [
"error", // treat architecture violations as lint errors
{
default: "allow", // allow normal imports unless a rule below explicitly disallows them
checkInternals: false, // do not apply these cross-module rules to imports inside the same module
rules: [
{
from: { type: "backend-module" }, // modules may depend on the shared types contract only as erased type-only imports
to: { type: "backend-shared-types" },
disallow: {
dependency: { kind: ["value", "typeof"] },
}, // block runtime imports so shared types stay a compile-time contract instead of a hidden shared module
message:
"Backend modules may only use `import type` when importing from server/shared/types.ts (or server/types.ts).",
},
{
to: { type: "backend-module" }, // when importing anything that belongs to another backend module
disallow: { to: { internalPath: "**" } }, // block all direct/deep imports into module internals by default
message:
"Cross-module imports must go through that module's barrel file (server/modules/<module>/index.ts or index.js).", // explicit error message for architecture violations
},
{
to: { type: "backend-module" }, // same target scope as the disallow rule above
allow: {
to: {
internalPath: [
"index", // allow extensionless barrel imports resolved as module root index
"index.{js,mjs,cjs,ts,tsx}", // allow explicit index.* barrel file imports
],
},
}, // re-allow only public module entry points (barrel files)
},
],
},
],
"boundaries/no-unknown": "error", // fail fast if boundaries cannot classify a dependency, which prevents silent rule bypasses
},
}
);