Files
lingxi/eslint.config.mjs
Stev_Wang b3a2a843bb build(ci): 建立单仓工具链与质量门禁
统一 pnpm 10 + Node 24.15.0 + Turborepo 单仓,落地 ESLint 9 flat、Prettier、commitlint、husky 与 Gitea CI,作为后续批次的一致门禁。
2026-11-02 17:20:17 +08:00

227 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 灵犀 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',
},
},
);