import parserVue from 'vue-eslint-parser' import js from '@eslint/js' import pluginTs from '@typescript-eslint/eslint-plugin' import parserTs from '@typescript-eslint/parser' import pluginTurbo from 'eslint-config-turbo/flat' import pluginComments from 'eslint-plugin-eslint-comments' import * as pluginImport from 'eslint-plugin-import-x' import pluginJsdoc from 'eslint-plugin-jsdoc' import pluginJsonc from 'eslint-plugin-jsonc' import pluginNode from 'eslint-plugin-n' import perfectionistPlugin from 'eslint-plugin-perfectionist' import pluginPrettier from 'eslint-plugin-prettier' import pluginRegexp from 'eslint-plugin-regexp' import pluginUnicorn from 'eslint-plugin-unicorn' import pluginUnusedImports from 'eslint-plugin-unused-imports' import pluginVue from 'eslint-plugin-vue' import globals from 'globals' import parserJsonc from 'jsonc-eslint-parser' export default [ { ignores: ['types/auto-import.d.ts', 'types/components.d.ts', 'dist/**', '.trae/**', 'build/**'], }, // vue 检查 ...pluginVue.configs['flat/essential'], ...pluginVue.configs['flat/strongly-recommended'], ...pluginVue.configs['flat/recommended'], { files: ['**/*.vue'], languageOptions: { parser: parserVue, globals: globals.browser, parserOptions: { ecmaFeatures: { jsx: true, }, extraFileExtensions: ['.vue'], parser: parserTs, sourceType: 'module', }, }, plugins: { vue: pluginVue, }, processor: pluginVue.processors['.vue'], rules: { ...pluginVue.configs.base.rules, 'vue/attribute-hyphenation': [ 'error', 'always', { ignore: [], }, ], 'vue/no-export-in-script-setup': 'off', 'vue/attributes-order': 'off', 'vue/block-order': [ 'error', { order: ['template', 'script', 'style'], }, ], 'vue/component-name-in-template-casing': ['error', 'PascalCase'], 'vue/component-options-name-casing': ['error', 'PascalCase'], 'vue/custom-event-name-casing': ['error', 'kebab-case'], 'vue/define-macros-order': [ 'error', { order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'], }, ], 'vue/dot-location': ['error', 'property'], 'vue/dot-notation': ['error', { allowKeywords: true }], 'vue/eqeqeq': ['error', 'smart'], 'vue/html-closing-bracket-newline': 'error', 'vue/html-indent': 'off', // 'vue/html-indent': ['error', 2], 'vue/html-quotes': ['error', 'double'], 'vue/html-self-closing': [ 'error', { html: { component: 'always', normal: 'never', void: 'always', }, math: 'always', svg: 'always', }, ], 'vue/max-attributes-per-line': 'off', 'vue/multi-word-component-names': 'off', 'vue/multiline-html-element-content-newline': 'error', 'vue/no-empty-pattern': 'error', 'vue/no-extra-parens': ['error', 'functions'], 'vue/no-irregular-whitespace': 'error', 'vue/no-loss-of-precision': 'error', 'vue/no-reserved-component-names': 'off', 'vue/no-restricted-syntax': [ 'error', 'DebuggerStatement', 'LabeledStatement', 'WithStatement', ], 'vue/no-restricted-v-bind': ['error', '/^v-/'], 'vue/no-sparse-arrays': 'error', 'vue/no-unused-refs': 'error', 'vue/no-useless-v-bind': 'error', 'vue/object-shorthand': [ 'error', 'always', { avoidQuotes: true, ignoreConstructors: false, }, ], 'vue/one-component-per-file': 'error', 'vue/prefer-import-from-vue': 'error', 'vue/prefer-separate-static-class': 'error', 'vue/prefer-template': 'error', 'vue/prop-name-casing': ['error', 'camelCase'], 'vue/require-default-prop': 'error', 'vue/require-explicit-emits': 'error', 'vue/require-prop-types': 'off', 'vue/singleline-html-element-content-newline': 'off', 'vue/space-infix-ops': 'error', 'vue/space-unary-ops': ['error', { nonwords: false, words: true }], 'vue/v-on-event-hyphenation': [ 'error', 'always', { autofix: true, ignore: [], }, ], }, }, // javascript 检查 { languageOptions: { ecmaVersion: 'latest', globals: { ...globals.browser, ...globals.es2021, ...globals.node, document: 'readonly', navigator: 'readonly', window: 'readonly', }, parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 'latest', sourceType: 'module', }, sourceType: 'module', }, linterOptions: { reportUnusedDisableDirectives: true, }, plugins: { 'unused-imports': pluginUnusedImports, }, rules: { ...js.configs.recommended.rules, 'accessor-pairs': ['error', { enforceForClassMembers: true, setWithoutGet: true }], 'array-callback-return': 'error', 'block-scoped-var': 'error', 'constructor-super': 'error', 'default-case-last': 'error', 'dot-notation': ['error', { allowKeywords: true }], eqeqeq: ['error', 'always'], 'keyword-spacing': 'off', 'new-cap': ['error', { capIsNew: false, newIsCap: true, properties: true }], 'no-alert': 'error', 'no-array-constructor': 'error', 'no-async-promise-executor': 'warn', 'no-caller': 'error', 'no-case-declarations': 'error', 'no-class-assign': 'error', 'no-compare-neg-zero': 'error', 'no-cond-assign': ['error', 'always'], 'no-console': ['warn', { allow: ['warn', 'error'] }], 'no-const-assign': 'error', 'no-control-regex': 'error', 'no-debugger': 'error', 'no-delete-var': 'error', 'no-dupe-args': 'error', 'no-dupe-class-members': 'error', 'no-dupe-keys': 'error', 'no-duplicate-case': 'error', 'no-empty': ['error', { allowEmptyCatch: true }], 'no-empty-character-class': 'error', 'no-empty-function': 'off', 'no-empty-pattern': 'error', 'no-eval': 'error', 'no-ex-assign': 'error', 'no-extend-native': 'error', 'no-extra-bind': 'error', 'no-extra-boolean-cast': 'error', 'no-fallthrough': 'error', 'no-func-assign': 'error', 'no-global-assign': 'error', 'no-implied-eval': 'error', 'no-import-assign': 'error', 'no-invalid-regexp': 'error', 'no-irregular-whitespace': 'error', 'no-iterator': 'error', 'no-labels': ['error', { allowLoop: false, allowSwitch: false }], 'no-lone-blocks': 'error', 'no-loss-of-precision': 'error', 'no-misleading-character-class': 'error', 'no-multi-str': 'error', 'no-new': 'error', 'no-new-func': 'error', 'no-new-object': 'error', 'no-new-symbol': 'error', 'no-new-wrappers': 'error', 'no-obj-calls': 'error', 'no-octal': 'error', 'no-octal-escape': 'error', 'no-proto': 'error', 'no-prototype-builtins': 'error', 'no-redeclare': ['error', { builtinGlobals: false }], 'no-regex-spaces': 'error', 'no-restricted-globals': [ 'error', { message: 'Use `globalThis` instead.', name: 'global' }, { message: 'Use `globalThis` instead.', name: 'self' }, ], 'no-restricted-properties': [ 'error', { message: 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.', property: '__proto__', }, { message: 'Use `Object.defineProperty` instead.', property: '__defineGetter__', }, { message: 'Use `Object.defineProperty` instead.', property: '__defineSetter__', }, { message: 'Use `Object.getOwnPropertyDescriptor` instead.', property: '__lookupGetter__', }, { message: 'Use `Object.getOwnPropertyDescriptor` instead.', property: '__lookupSetter__', }, ], 'no-restricted-syntax': [ 'error', 'DebuggerStatement', 'LabeledStatement', 'WithStatement', 'TSEnumDeclaration[const=true]', 'TSExportAssignment', ], 'no-self-assign': ['error', { props: true }], 'no-self-compare': 'error', 'no-sequences': 'error', 'no-shadow-restricted-names': 'error', 'no-sparse-arrays': 'error', 'no-template-curly-in-string': 'error', 'no-this-before-super': 'error', 'no-throw-literal': 'error', 'no-undef': 'off', 'no-undef-init': 'error', 'no-unexpected-multiline': 'error', 'no-unmodified-loop-condition': 'error', 'no-unneeded-ternary': ['error', { defaultAssignment: false }], 'no-unreachable': 'error', 'no-unreachable-loop': 'error', 'no-unsafe-finally': 'error', 'no-unsafe-negation': 'error', 'no-unused-expressions': [ 'error', { allowShortCircuit: true, allowTaggedTemplates: true, allowTernary: true, }, ], 'no-unused-vars': [ 'error', { args: 'none', caughtErrors: 'none', ignoreRestSiblings: true, vars: 'all', }, ], 'no-use-before-define': ['error', { classes: false, functions: false, variables: false }], 'no-useless-backreference': 'error', 'no-useless-call': 'error', 'no-useless-catch': 'error', 'no-useless-computed-key': 'error', 'no-useless-constructor': 'error', 'no-useless-rename': 'error', 'no-useless-return': 'error', 'no-var': 'error', 'no-with': 'error', 'object-shorthand': ['error', 'always', { avoidQuotes: true, ignoreConstructors: false }], 'one-var': ['error', { initialized: 'never' }], 'prefer-arrow-callback': [ 'error', { allowNamedFunctions: false, allowUnboundThis: true, }, ], 'prefer-const': [ 'error', { destructuring: 'all', ignoreReadBeforeAssign: true, }, ], 'prefer-exponentiation-operator': 'error', 'prefer-promise-reject-errors': 'error', 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], 'prefer-rest-params': 'error', 'prefer-spread': 'error', 'prefer-template': 'error', 'space-before-function-paren': 'off', 'spaced-comment': 'error', 'symbol-description': 'error', 'unicode-bom': ['error', 'never'], 'unused-imports/no-unused-imports': 'error', 'unused-imports/no-unused-vars': [ 'error', { args: 'after-used', argsIgnorePattern: '^_', vars: 'all', varsIgnorePattern: '^_', }, ], 'use-isnan': ['error', { enforceForIndexOf: true, enforceForSwitchCase: true }], 'valid-typeof': ['error', { requireStringLiterals: true }], 'vars-on-top': 'error', yoda: ['error', 'never'], }, }, { ignores: [ 'dist/**', 'node_modules/**', '*.d.ts', 'public/**', '**/*.lock', '**/*.sh', '**/*.ttf', '**/*.woff', '**/.turbo', '**/.nuxt', '**/pnpm-lock.yaml', '**/.vercel', '**/.changeset', '**/.idea', '**/.cache', '**/.output', '**/CHANGELOG*.md', '**/LICENSE*', '**/temp', '**/.temp', '**/tmp', '**/.tmp', '**/.history', ], }, // turborepo 规则 { plugins: { turbo: pluginTurbo, }, }, // 查找正则表达式错误和正则表达式样式指南违规的 { plugins: { regexp: pluginRegexp, }, rules: { ...pluginRegexp.configs.recommended.rules, }, }, // 超过 100 条强大的 ESLint 规则 { plugins: { unicorn: pluginUnicorn, }, rules: { ...pluginUnicorn.configs.recommended.rules, 'unicorn/no-array-reduce': 'off', 'unicorn/better-regex': 'off', 'unicorn/consistent-destructuring': 'off', 'unicorn/consistent-function-scoping': 'off', 'unicorn/expiring-todo-comments': 'off', 'unicorn/filename-case': 'off', 'unicorn/import-style': 'off', 'unicorn/no-array-for-each': 'off', 'unicorn/no-null': 'off', 'unicorn/no-useless-undefined': 'off', 'unicorn/prefer-at': 'off', 'unicorn/prefer-dom-node-text-content': 'off', 'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }], 'unicorn/prefer-global-this': 'off', 'unicorn/prefer-top-level-await': 'off', 'unicorn/prevent-abbreviations': 'off', 'unicorn/no-nested-ternary': 'off', }, }, { files: ['**/*.?([cm])[jt]s?(x)'], rules: { 'unicorn/no-process-exit': 'off', }, }, // JSDoc 规则 { plugins: { jsdoc: pluginJsdoc, }, rules: { 'jsdoc/check-access': 'warn', 'jsdoc/check-param-names': 'warn', 'jsdoc/check-property-names': 'warn', 'jsdoc/check-types': 'warn', 'jsdoc/empty-tags': 'warn', 'jsdoc/implements-on-classes': 'warn', 'jsdoc/no-defaults': 'warn', 'jsdoc/no-multi-asterisks': 'warn', 'jsdoc/require-param-name': 'warn', 'jsdoc/require-property': 'warn', 'jsdoc/require-property-description': 'warn', 'jsdoc/require-property-name': 'warn', 'jsdoc/require-returns-check': 'warn', 'jsdoc/require-returns-description': 'warn', 'jsdoc/require-yields-check': 'warn', }, }, // 注释规则 { plugins: { 'eslint-comments': pluginComments, }, rules: { 'eslint-comments/no-aggregating-enable': 'error', 'eslint-comments/no-duplicate-disable': 'error', 'eslint-comments/no-unlimited-disable': 'error', 'eslint-comments/no-unused-enable': 'error', }, }, // 自动排序和整理对象、导入、类型、枚举和 JSX 属性 perfectionistPlugin.configs['recommended-natural'], { rules: { 'perfectionist/sort-exports': [ 'error', { order: 'asc', type: 'natural', }, ], 'perfectionist/sort-imports': [ 'error', { customGroups: { type: { 'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'], }, value: { vue: ['^vue$', '^vue-.+', '^@vue/.+'], }, }, environment: 'node', groups: [ ['external-type', 'builtin-type', 'type'], 'vue-type', ['parent-type', 'sibling-type', 'index-type'], 'builtin', 'vue', 'external', ['parent', 'sibling', 'index'], 'side-effect', 'side-effect-style', 'style', 'object', 'unknown', ], internalPattern: ['^#/.+'], newlinesBetween: 'always', order: 'asc', type: 'natural', }, ], 'perfectionist/sort-modules': 'off', 'perfectionist/sort-named-exports': [ 'error', { order: 'asc', type: 'natural', }, ], 'perfectionist/sort-objects': [ 'off', { customGroups: { items: 'items', list: 'list', children: 'children', }, groups: ['unknown', 'items', 'list', 'children'], ignorePattern: ['children'], order: 'asc', type: 'natural', }, ], }, }, // node.js 的 ESLint 规则 { plugins: { n: pluginNode, }, rules: { 'n/handle-callback-err': ['error', '^(err|error)$'], 'n/no-deprecated-api': 'error', 'n/no-exports-assign': 'error', 'n/no-extraneous-import': [ 'error', { allowModules: ['unbuild', 'vitest', 'vite', '@vue/test-utils', '@playwright/test'], }, ], 'n/no-new-require': 'error', 'n/no-path-concat': 'error', 'n/no-unsupported-features/es-syntax': [ 'error', { ignores: [], version: '>=18.0.0', }, ], 'n/prefer-global/buffer': ['error', 'never'], 'n/prefer-global/process': ['error', 'always'], 'n/process-exit-as-throw': 'error', }, }, { files: ['**/*.?([cm])[jt]s?(x)'], rules: { 'n/prefer-global/process': 'off', }, }, // 支持 es6+ 导入/导出语法的校验,并防止文件路径和导入名称拼写错误的问题 { plugins: { import: pluginImport, }, rules: { 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/first': 'error', 'import/newline-after-import': 'error', 'import/no-duplicates': 'error', 'import/no-mutable-exports': 'error', 'import/no-named-default': 'error', 'import/no-self-import': 'error', 'import/no-unresolved': 'off', 'import/no-webpack-loader-syntax': 'error', }, }, // 禁用部分 typescript 规则校验 { files: ['**/__tests__/**/*.?([cm])[jt]s?(x)'], name: 'disables/test', rules: { '@typescript-eslint/ban-ts-comment': 'off', // 不允许 @ts- 注释或要求指令后有描述。 'no-console': 'off', // 不允许使用 console }, }, { files: ['**/*.d.ts'], name: 'disables/dts', rules: { '@typescript-eslint/triple-slash-reference': 'off', // 不允许使用某些三斜杠指令,而使用 ES6 风格的导入声明 }, }, { files: ['**/*.js', '**/*.mjs', '**/*.cjs'], name: 'disables/js', rules: { '@typescript-eslint/explicit-module-boundary-types': 'off', // 要求导出函数和类的公共类方法具有明确的返回和参数类型 }, }, // 检查 JSON 、 JSONC 和 JSON5 文件 { files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'], languageOptions: { parser: parserJsonc, }, plugins: { jsonc: pluginJsonc, }, rules: { 'jsonc/no-bigint-literals': 'error', 'jsonc/no-binary-expression': 'error', 'jsonc/no-binary-numeric-literals': 'error', 'jsonc/no-dupe-keys': 'error', 'jsonc/no-escape-sequence-in-identifier': 'error', 'jsonc/no-floating-decimal': 'error', 'jsonc/no-hexadecimal-numeric-literals': 'error', 'jsonc/no-infinity': 'error', 'jsonc/no-multi-str': 'error', 'jsonc/no-nan': 'error', 'jsonc/no-number-props': 'error', 'jsonc/no-numeric-separators': 'error', 'jsonc/no-octal': 'error', 'jsonc/no-octal-escape': 'error', 'jsonc/no-octal-numeric-literals': 'error', 'jsonc/no-parenthesized': 'error', 'jsonc/no-plus-sign': 'error', 'jsonc/no-regexp-literals': 'error', 'jsonc/no-sparse-arrays': 'error', 'jsonc/no-template-literals': 'error', 'jsonc/no-undefined-value': 'error', 'jsonc/no-unicode-codepoint-escapes': 'error', 'jsonc/no-useless-escape': 'error', 'jsonc/space-unary-ops': 'error', 'jsonc/valid-json-number': 'error', 'jsonc/vue-custom-block/no-parsing-error': 'error', }, }, // package.json 文件排序 { files: ['**/package.json'], rules: { 'jsonc/sort-array-values': [ 'error', { order: { type: 'asc' }, pathPattern: '^files$|^pnpm.neverBuiltDependencies$', }, ], 'jsonc/sort-keys': [ 'error', { order: [ 'name', 'version', 'description', 'private', 'keywords', 'homepage', 'bugs', 'repository', 'license', 'author', 'contributors', 'categories', 'funding', 'type', 'scripts', 'files', 'sideEffects', 'bin', 'main', 'module', 'unpkg', 'jsdelivr', 'types', 'typesVersions', 'imports', 'exports', 'publishConfig', 'icon', 'activationEvents', 'contributes', 'peerDependencies', 'peerDependenciesMeta', 'dependencies', 'optionalDependencies', 'devDependencies', 'engines', 'packageManager', 'pnpm', 'overrides', 'resolutions', 'husky', 'simple-git-hooks', 'lint-staged', 'eslintConfig', ], pathPattern: '^$', }, { order: { type: 'asc' }, pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$', }, { order: { type: 'asc' }, pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$', }, { order: ['types', 'import', 'require', 'default'], pathPattern: '^exports.*$', }, ], }, }, // tsconfig.json 文件排序 { files: ['**/tsconfig.json', '**/tsconfig.*.json'], rules: { 'jsonc/sort-keys': [ 'error', { order: ['extends', 'compilerOptions', 'references', 'files', 'include', 'exclude'], pathPattern: '^$', }, { order: [ /* Projects */ 'incremental', 'composite', 'tsBuildInfoFile', 'disableSourceOfProjectReferenceRedirect', 'disableSolutionSearching', 'disableReferencedProjectLoad', /* Language and Environment */ 'target', 'jsx', 'jsxFactory', 'jsxFragmentFactory', 'jsxImportSource', 'lib', 'moduleDetection', 'noLib', 'reactNamespace', 'useDefineForClassFields', 'emitDecoratorMetadata', 'experimentalDecorators', /* Modules */ 'baseUrl', 'rootDir', 'rootDirs', 'customConditions', 'module', 'moduleResolution', 'moduleSuffixes', 'noResolve', 'paths', 'resolveJsonModule', 'resolvePackageJsonExports', 'resolvePackageJsonImports', 'typeRoots', 'types', 'allowArbitraryExtensions', 'allowImportingTsExtensions', 'allowUmdGlobalAccess', /* JavaScript Support */ 'allowJs', 'checkJs', 'maxNodeModuleJsDepth', /* Type Checking */ 'strict', 'strictBindCallApply', 'strictFunctionTypes', 'strictNullChecks', 'strictPropertyInitialization', 'allowUnreachableCode', 'allowUnusedLabels', 'alwaysStrict', 'exactOptionalPropertyTypes', 'noFallthroughCasesInSwitch', 'noImplicitAny', 'noImplicitOverride', 'noImplicitReturns', 'noImplicitThis', 'noPropertyAccessFromIndexSignature', 'noUncheckedIndexedAccess', 'noUnusedLocals', 'noUnusedParameters', 'useUnknownInCatchVariables', /* Emit */ 'declaration', 'declarationDir', 'declarationMap', 'downlevelIteration', 'emitBOM', 'emitDeclarationOnly', 'importHelpers', 'importsNotUsedAsValues', 'inlineSourceMap', 'inlineSources', 'mapRoot', 'newLine', 'noEmit', 'noEmitHelpers', 'noEmitOnError', 'outDir', 'outFile', 'preserveConstEnums', 'preserveValueImports', 'removeComments', 'sourceMap', 'sourceRoot', 'stripInternal', /* Interop Constraints */ 'allowSyntheticDefaultImports', 'esModuleInterop', 'forceConsistentCasingInFileNames', 'isolatedModules', 'preserveSymlinks', 'verbatimModuleSyntax', /* Completeness */ 'skipDefaultLibCheck', 'skipLibCheck', ], pathPattern: '^compilerOptions$', }, ], }, }, // typescript 规则 { files: ['**/*.?([cm])[jt]s?(x)'], languageOptions: { parser: parserTs, parserOptions: { createDefaultProgram: false, ecmaFeatures: { jsx: true, }, ecmaVersion: 'latest', extraFileExtensions: ['.vue'], jsxPragma: 'React', project: './tsconfig.*.json', sourceType: 'module', }, }, plugins: { '@typescript-eslint': pluginTs, }, rules: { ...pluginTs.configs['eslint-recommended'].overrides?.[0].rules, ...pluginTs.configs.strict.rules, '@typescript-eslint/ban-ts-comment': [ 'error', { 'ts-check': false, 'ts-expect-error': 'allow-with-description', 'ts-ignore': 'allow-with-description', 'ts-nocheck': 'allow-with-description', }, ], // '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], '@typescript-eslint/consistent-type-definitions': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-empty-function': [ 'error', { allow: ['arrowFunctions', 'functions', 'methods'], }, ], '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/no-non-null-assertion': 'error', '@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', }, ], '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-var-requires': 'error', 'unused-imports/no-unused-vars': 'off', }, }, // prettier 规则 { plugins: { prettier: pluginPrettier, }, rules: { 'prettier/prettier': 'warn', }, }, ]