// 灵犀 ESLint 9 flat config // 依据:docs/工程规范/03-格式化与Lint.md // 分层:根规则(全仓库)→ apps/web(React)→ packages/ui(图标统一出口)→ scripts(允许 console) import js from '@eslint/js'; import importX from 'eslint-plugin-import-x'; import jsxA11y from 'eslint-plugin-jsx-a11y'; import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; import simpleImportSort from 'eslint-plugin-simple-import-sort'; import globals from 'globals'; import tseslint from 'typescript-eslint'; const UI_LIB_MESSAGE = 'UI 组件库只能用 @douyinfe/semi-ui-19(工程规范 03 §3.2);禁装其他组件库。'; const ICON_MESSAGE = '图标只能从 @lingxi/ui/icons 按语义名导入;禁止直接引用图标库或内联 SVG(AGENTS §3.3)。'; const COLOR_MESSAGE = '业务代码禁止颜色字面量;颜色只能定义在 packages/ui/src/theme/ 的 token 中(工程规范 03 §3.2)。'; export default tseslint.config( { ignores: [ '**/node_modules/**', '**/dist/**', '**/coverage/**', '**/.turbo/**', '.tools/**', 'design/**', 'docs/**', 'openapi/**', 'prisma/migrations/**', '项目参考/**', 'playwright-report/**', 'test-results/**', '**/*.d.ts', ], }, js.configs.recommended, ...tseslint.configs.strictTypeChecked, ...tseslint.configs.stylisticTypeChecked, { name: 'lingxi/base', languageOptions: { globals: { ...globals.node, ...globals.browser, ...globals.es2022 }, parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, plugins: { 'import-x': importX, 'simple-import-sort': simpleImportSort, }, settings: { 'import-x/resolver': { typescript: { project: ['./tsconfig.json', './apps/*/tsconfig.json', './packages/*/tsconfig.json'], }, }, }, linterOptions: { reportUnusedDisableDirectives: 'error' }, rules: { // ---- TypeScript 硬约束(AGENTS §3.2)---- '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports', fixStyle: 'separate-type-imports' }, ], '@typescript-eslint/no-unnecessary-condition': 'error', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-non-null-assertion': 'warn', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, ], '@typescript-eslint/restrict-template-expressions': [ 'error', { allowNumber: true, allowBoolean: true }, ], '@typescript-eslint/naming-convention': [ 'error', { selector: 'typeLike', format: ['PascalCase'] }, { selector: 'enumMember', format: ['UPPER_CASE'] }, ], '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], // ---- 导入与依赖方向 ---- 'import-x/no-cycle': ['error', { maxDepth: 8, ignoreExternal: false }], 'import-x/no-self-import': 'error', 'simple-import-sort/imports': 'error', 'simple-import-sort/exports': 'error', // ---- 通用 ---- 'no-console': 'error', eqeqeq: ['error', 'always'], 'no-var': 'error', 'prefer-const': 'error', complexity: ['warn', { max: 15 }], 'max-lines-per-function': [ 'warn', { max: 50, skipBlankLines: true, skipComments: true, IIFEs: true }, ], }, }, // 纯 JS 配置文件不做类型检查 { name: 'lingxi/js-config', files: ['**/*.{js,mjs,cjs}'], extends: [tseslint.configs.disableTypeChecked], rules: { 'no-console': 'off', 'import-x/no-cycle': 'off', }, }, // 应用层:Web(React 19) { name: 'lingxi/apps-web', files: ['apps/web/**/*.{ts,tsx}'], plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh, 'jsx-a11y': jsxA11y, }, rules: { ...reactHooks.configs.recommended.rules, 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], 'jsx-a11y/alt-text': 'error', 'jsx-a11y/no-autofocus': 'warn', '@typescript-eslint/no-restricted-imports': [ 'error', { patterns: [ { group: ['@douyinfe/semi-ui', '@douyinfe/semi-ui/*'], message: UI_LIB_MESSAGE }, { group: [ '@remixicon/react', 'react-icons', 'react-icons/*', 'lucide-react', 'lucide', 'semi-icons', '@douyinfe/semi-icons', '@heroicons/*', '@ant-design/icons', ], message: ICON_MESSAGE, }, { group: [ 'antd', '@arco-design/web-react', 'element-plus', 'naive-ui', '@mui/*', '@chakra-ui/*', ], message: UI_LIB_MESSAGE, }, ], }, ], 'no-restricted-syntax': [ 'error', { selector: 'Literal[value=/^#(?:[0-9a-fA-F]{3,8})$/]', message: COLOR_MESSAGE, }, { selector: 'Literal[value=/^rgba?\\(/]', message: COLOR_MESSAGE, }, { selector: "JSXOpeningElement[name.name='svg']", message: ICON_MESSAGE, }, ], }, }, // packages/ui:图标统一出口(只有 icons.ts 可直接引用 Remix Icon) { name: 'lingxi/ui-icons', files: ['packages/ui/src/**/*.{ts,tsx}'], rules: { '@typescript-eslint/no-restricted-imports': [ 'error', { patterns: [{ group: ['@remixicon/react'], message: ICON_MESSAGE }] }, ], }, }, { name: 'lingxi/ui-icons-exit', files: ['packages/ui/src/icons.ts'], rules: { '@typescript-eslint/no-restricted-imports': ['error', { patterns: [] }], }, }, // Nest 模块装饰器类按框架约定是空类 { name: 'lingxi/nest-modules', files: ['apps/api/**/*.module.ts'], rules: { '@typescript-eslint/no-extraneous-class': 'off', }, }, // 脚本层:允许 console(CLI 输出) { name: 'lingxi/scripts', files: ['scripts/**/*.{ts,mjs,js}', '**/scripts/**/*.ts', 'prisma/seed.ts', 'e2e/**/*.ts'], rules: { 'no-console': 'off', '@typescript-eslint/no-non-null-assertion': 'off', }, }, // 测试文件:允许更松的表达 { name: 'lingxi/tests', files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.int.spec.ts', 'e2e/**/*.ts'], rules: { '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-argument': 'off', 'max-lines-per-function': 'off', complexity: 'off', }, }, );