import eslintJs from '@eslint/js' import typescriptEslint from '@typescript-eslint/eslint-plugin' import typescriptParser from '@typescript-eslint/parser' import { defineConfig } from 'eslint/config' import expoConfig from 'eslint-config-expo/flat.js' import i18nJsonPlugin from 'eslint-plugin-i18n-json' import prettierPlugin from 'eslint-plugin-prettier' import reactCompilerPlugin from 'eslint-plugin-react-compiler' import reactNativePlugin from 'eslint-plugin-react-native' import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort' import tailwindcssPlugin from 'eslint-plugin-tailwindcss' import unicornPlugin from 'eslint-plugin-unicorn' import unusedImportsPlugin from 'eslint-plugin-unused-imports' import path from 'path' import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) // 共享的插件配置 (移除可能与 expo 冲突的插件) const sharedPlugins = { prettier: prettierPlugin, 'unused-imports': unusedImportsPlugin, 'simple-import-sort': simpleImportSortPlugin, tailwindcss: tailwindcssPlugin, unicorn: unicornPlugin, 'react-native': reactNativePlugin, } // 共享的规则配置 const sharedRules = { // Prettier 配置 'prettier/prettier': [ 'error', { singleQuote: true, endOfLine: 'auto', trailingComma: 'all', printWidth: 120, tabWidth: 2, semi: false, arrowParens: 'always', bracketSameLine: false, bracketSpacing: true, jsxSingleQuote: false, useTabs: false, quoteProps: 'as-needed', plugins: ['prettier-plugin-tailwindcss'], }, { usePrettierrc: false }, ], // 通用规则 'max-params': ['warn', 3], // 'max-lines-per-function': ['error', 800], // Import 规则 'import/prefer-default-export': 'off', 'import/no-cycle': ['error', { maxDepth: '∞' }], // Import 排序 'simple-import-sort/imports': 'error', 'simple-import-sort/exports': 'error', // 未使用的导入 'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-vars': [ 'warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', }, ], // Tailwind CSS 规则 'tailwindcss/classnames-order': 'off', 'tailwindcss/enforces-negative-arbitrary-values': 'error', 'tailwindcss/enforces-shorthand': 'error', 'tailwindcss/migration-from-tailwind-2': 'error', 'tailwindcss/no-arbitrary-value': 'off', 'tailwindcss/no-contradicting-classname': 'error', 'tailwindcss/no-custom-classname': 'off', 'tailwindcss/no-unnecessary-arbitrary-value': 'error', // React 规则 'react/display-name': 'off', 'react/destructuring-assignment': 'off', 'react/require-default-props': 'off', 'react/no-unescaped-entities': 'off', 'react-native/no-raw-text': 'off', 'no-undef': 'off', // JSX 属性换行规则 'react/jsx-max-props-per-line': [ 'error', { maximum: 10, // 每行最多一个属性 when: 'multiline', // 仅在多行时生效 }, ], // JSX 第一个属性换行规则 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], // React Native 规则 'react-native/no-unused-styles': 'off', 'react-native/split-platform-components': 'off', 'react-native/no-inline-styles': 'off', 'react-native/no-color-literals': 'off', 'react-native/no-single-element-style-arrays': 'off', 'react-hooks/rules-of-hooks': 'warn', } export default defineConfig([ // Base recommended config eslintJs.configs.recommended, // Expo config - 添加 expo 配置 expoConfig, // Global ignores { ignores: [ 'node_modules/**', 'ios/', 'android/', '.expo/', 'assets/', '**/.vscode', '**/.idea', 'dist/', '**/app.json', '**/eas.json', ], }, // Base configuration for JavaScript files { files: ['**/*.js', '**/*.jsx', '**/*.mjs'], plugins: sharedPlugins, rules: { ...sharedRules, }, languageOptions: { globals: { __DEV__: 'readonly', __dirname: 'readonly', __filename: 'readonly', console: 'readonly', global: 'readonly', process: 'readonly', Buffer: 'readonly', }, ecmaVersion: 'latest', sourceType: 'module', }, settings: { 'import/resolver': { node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], moduleDirectory: ['node_modules', './'], }, }, }, }, // TypeScript files configuration { files: ['**/*.ts', '**/*.tsx'], languageOptions: { parser: typescriptParser, parserOptions: { project: './tsconfig.json', ecmaFeatures: { jsx: true, }, ecmaVersion: 'latest', sourceType: 'module', tsconfigRootDir: __dirname, }, globals: { __DEV__: 'readonly', __dirname: 'readonly', __filename: 'readonly', console: 'readonly', global: 'readonly', process: 'readonly', Buffer: 'readonly', }, }, plugins: { ...sharedPlugins, '@typescript-eslint': typescriptEslint, 'react-compiler': reactCompilerPlugin, }, rules: { ...sharedRules, // TypeScript 特有规则 '@typescript-eslint/comma-dangle': 'off', '@typescript-eslint/consistent-type-imports': [ 'warn', { prefer: 'type-imports', fixStyle: 'inline-type-imports', disallowTypeAnnotations: true, }, ], '@typescript-eslint/no-unused-vars': 'off', // 禁止使用 any 类型的规则 '@typescript-eslint/no-explicit-any': 'warn', // 禁止将 any 类型作为参数传递 '@typescript-eslint/no-unsafe-argument': 'warn', // 禁止将 any 类型赋值给其他变量 '@typescript-eslint/no-unsafe-assignment': 'warn', // 禁止调用 any 类型的函数 '@typescript-eslint/no-unsafe-call': 'warn', // 禁止访问 any 类型的成员 '@typescript-eslint/no-unsafe-member-access': 'warn', // 禁止从函数返回 any 类型 '@typescript-eslint/no-unsafe-return': 'warn', // React Compiler rules 'react-compiler/react-compiler': 'error', // React Hooks rules - 禁用在回调中使用 hooks 的检查 'react-hooks/rules-of-hooks': 'warn', // Unicorn 特有配置 (覆盖共享配置) 'unicorn/prefer-module': 'off', // Expo-specific rules 'no-undef': 'off', // TypeScript handles this }, settings: { 'import/resolver': { typescript: { alwaysTryTypes: true, project: path.resolve(__dirname, './tsconfig.json'), }, node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], moduleDirectory: ['node_modules', './'], }, }, }, }, // Translation files configuration { files: ['translations/*.json'], plugins: { 'i18n-json': i18nJsonPlugin, }, processor: { meta: { name: '.json' }, ...i18nJsonPlugin.processors['.json'], }, rules: { ...i18nJsonPlugin.configs.recommended.rules, // 消息值的语法是否符合特定的国际化格式要求 'i18n-json/valid-message-syntax': [ 2, { syntax: path.resolve('./scripts/i18next-syntax-validation.js'), }, ], // 是否包含有效的 JSON 语法 'i18n-json/valid-json': 2, // 按照特定的顺序排列 // 关闭解决新加字段覆盖问题 'i18n-json/sorted-keys': [ 2, { order: 'asc', indentSpaces: 4, }, ], // 检查是否有缺失的键或者多出来的键 'i18n-json/identical-keys': [ 2, { filePath: path.resolve('./translations/en-US.json'), }, ], }, }, // 组件文件必须使用 PascalCase { files: ['**/components/**/*.tsx', '**/components/**/*.jsx', '**/@share/components/**/*.tsx'], rules: { 'unicorn/filename-case': [ 'warn', { case: 'pascalCase', ignore: ['\\.native\\.', '\\.web\\.', 'index\\.'], }, ], }, }, // hooks、utils、lib 等使用 camelCase { files: ['**/hooks/**/*.ts', '**/utils/**/*.ts', '**/lib/**/*.ts', '**/constants/**/*.ts'], rules: { 'unicorn/filename-case': [ 'warn', { case: 'camelCase', ignore: ['\\.native\\.', '\\.web\\.', 'index\\.'], }, ], }, }, ])