import { defineConfig, globalIgnores } from "eslint/config"; import nextVitals from "eslint-config-next/core-web-vitals"; import nextTs from "eslint-config-next/typescript"; import { fileURLToPath, pathToFileURL } from "node:url"; import { dirname, join } from "node:path"; const __dirname = dirname(fileURLToPath(import.meta.url)); const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, { rules: { "react-hooks/incompatible-library": "off", "@typescript-eslint/no-unused-vars": [ "warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_", }, ], // 禁止硬编码 hex 颜色字面量(白名单:tokens 定义文件、邮件、manifest) // 白名单文件内的 #hex 需在所在行上方加 // eslint-disable-next-line no-restricted-syntax 注释豁免 "no-restricted-syntax": [ "error", { selector: "Literal[value=/#[0-9a-fA-F]{3,8}/]", message: "禁止硬编码 hex 颜色,使用设计令牌 hsl(var(--*)) 或 Tailwind 类 bg-*", }, ], // 禁止 console.* 调用,统一使用 createModuleLogger("module-name") // 豁免场景:scripts/、tests/、客户端 .tsx(留待客户端错误上报机制处理)、被客户端导入的 .ts "no-console": "error", }, }, { files: ["tests/**/*.ts"], languageOptions: { globals: { describe: "readonly", it: "readonly", test: "readonly", expect: "readonly", beforeAll: "readonly", afterAll: "readonly", beforeEach: "readonly", afterEach: "readonly", vi: "readonly", }, }, }, // 自定义规则:检测硬编码字体家族字面量 { plugins: { "design-tokens": { rules: { "no-hardcoded-fonts": await import( pathToFileURL(join(__dirname, "eslint-rules/no-hardcoded-design-tokens.js")).href ).then((m) => m.default ?? m), }, }, }, rules: { "design-tokens/no-hardcoded-fonts": "error", }, }, // 缓存策略规则:禁止在 actions / route.ts 中直接调用 revalidatePath/revalidateTag { files: ["src/modules/**/actions*.ts", "src/app/api/**/route.ts", "src/i18n/actions*.ts"], rules: { "no-restricted-syntax": [ "error", { selector: "CallExpression[callee.name='revalidatePath']", message: "使用 invalidateFor() 替代直接 revalidatePath(),参见 docs/architecture/004 缓存章节", }, { selector: "CallExpression[callee.name='revalidateTag']", message: "使用 invalidateFor() 替代直接 revalidateTag(),参见 docs/architecture/004 缓存章节", }, ], }, }, // 豁免:shared/lib/cache/ 内部允许调用 revalidatePath/revalidateTag(invalidate.ts 内部实现) { files: ["src/shared/lib/cache/**/*.ts"], rules: { "no-restricted-syntax": "off", }, }, // 豁免:scripts/ 和 tests/ 允许 console(脚本/测试场景不接入 logger) { files: ["scripts/**/*.{js,mjs,ts}", "tests/**/*.{ts,tsx,js}"], rules: { "no-console": "off", }, }, // 暂时豁免:客户端 .tsx 文件中的 console 调用,留待客户端错误上报机制(Task 12-14)处理 // 客户端组件不能导入服务端 pino logger,需通过 useErrorReport Hook 上报 { files: ["src/**/*.tsx"], rules: { "no-console": "off", }, }, // 豁免:客户端 hooks(.ts)被客户端组件导入,不能使用服务端 logger // 留待 Task 12-14 客户端错误上报机制处理 { files: ["src/**/hooks/**/*.ts"], rules: { "no-console": "off", }, }, // 豁免:components 目录中的 .ts utils(如 exam-preview-utils.ts)被客户端 hooks 导入 { files: ["src/**/components/**/*.ts"], rules: { "no-console": "off", }, }, // 豁免:被客户端组件导入的 .ts 文件不能使用服务端 logger(如 query-client.ts 被 providers.tsx 导入) { files: ["src/shared/lib/query-client.ts"], rules: { "no-console": "off", }, }, // Override default ignores of eslint-config-next. globalIgnores([ // Default ignores of eslint-config-next: ".next/**", "out/**", "build/**", "next-env.d.ts", "docs/scripts/**", "playwright-report/**", "test-results/**", // Debug scripts using CommonJS "tests/webapp/debug_drizzle.js", // Migration/maintenance scripts using CommonJS require() "scripts/**/*.js", // 已删除文件的归档目录,不参与 lint "deletes/**", ]), ]); export default eslintConfig;