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' // 由于 eslint-plugin-unicorn 在解析时出现语法错误,暂时移除该插件 // const unicornPlugin = await import('eslint-plugin-unicorn').catch(() => null) 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: 100, tabWidth: 4, semi: false, arrowParens: 'always', bracketSameLine: false, bracketSpacing: true, jsxSingleQuote: false, useTabs: false, quoteProps: 'as-needed', plugins: ['prettier-plugin-tailwindcss'], }, { usePrettierrc: false }, ], // 通用规则 'max-params': ['error', 3], 'max-lines-per-function': ['error', 500], // 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': [ 'error', { 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', // Unicorn 规则 'unicorn/filename-case': [ 'error', { case: 'kebabCase', ignore: ['/android', '/ios'], }, ], // React 规则 'react/display-name': 'off', 'react/destructuring-assignment': 'off', 'react/require-default-props': 'off', 'react/no-unescaped-entities': 'off', // React/JSX 属性排序规则 'react/jsx-sort-props': [ 'error', { callbacksLast: true, // 回调函数属性放在最后 shorthandFirst: true, // 简写属性放在前面 shorthandLast: false, multiline: 'last', // 多行属性放在最后 ignoreCase: true, // 忽略大小写 noSortAlphabetically: false, reservedFirst: true, // 保留属性(如 key, ref)放在最前面 }, ], // JSX 属性换行规则 'react/jsx-max-props-per-line': [ 'error', { maximum: 1, // 每行最多一个属性 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-raw-text': 'error', 'react-native/no-single-element-style-arrays': 'off', // 限制导入规则 - 强制使用自定义 Text 组件 'no-restricted-imports': [ 'error', { paths: [ { name: 'react-native', importNames: ['Text'], message: 'please use @/components/ui instead of react-native Text', }, ], patterns: [ { group: ['react-native'], importNames: ['Text'], message: 'please use @/components/ui instead of react-native Text', }, ], }, ], } 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, 'no-undef': 'off', }, 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': 'error', // 禁止将 any 类型作为参数传递 '@typescript-eslint/no-unsafe-argument': 'error', // 禁止将 any 类型赋值给其他变量 '@typescript-eslint/no-unsafe-assignment': 'error', // 禁止调用 any 类型的函数 '@typescript-eslint/no-unsafe-call': 'error', // 禁止访问 any 类型的成员 '@typescript-eslint/no-unsafe-member-access': 'error', // 禁止从函数返回 any 类型 '@typescript-eslint/no-unsafe-return': 'error', // React Compiler rules 'react-compiler/react-compiler': 'error', // 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'), }, ], }, }, ])