Skip to content
On this page

ESLint 插件系统

ESLint 插件是扩展 ESLint 功能的主要方式,通过插件可以添加自定义规则、环境、配置等。

什么是 ESLint 插件

ESLint 插件是一个 npm 包,通常以 eslint-plugin- 开头命名,它提供额外的规则、环境或其他功能来扩展 ESLint 的能力。

安装和使用插件

安装插件

bash
# 安装 React 插件
npm install --save-dev eslint-plugin-react

# 安装 import 插件
npm install --save-dev eslint-plugin-import

# 安装 JSX accessibility 插件
npm install --save-dev eslint-plugin-jsx-a11y

# 安装 Jest 插件
npm install --save-dev eslint-plugin-jest

# 安装 Prettier 插件
npm install --save-dev eslint-plugin-prettier eslint-config-prettier

配置插件

javascript
// .eslintrc.js
module.exports = {
  // 使用插件
  plugins: [
    'react',           // 对应 eslint-plugin-react
    'import',          // 对应 eslint-plugin-import
    'jsx-a11y',        // 对应 eslint-plugin-jsx-a11y
    'jest'             // 对应 eslint-plugin-jest
  ],
  
  // 使用插件提供的规则
  rules: {
    'react/jsx-uses-react': 'error',
    'react/jsx-uses-vars': 'error',
    'import/no-unresolved': 'error',
    'jsx-a11y/alt-text': 'error'
  }
};

常用插件详解

1. React 插件

javascript
// .eslintrc.js
module.exports = {
  plugins: [
    'react'
  ],
  
  extends: [
    'plugin:react/recommended'
  ],
  
  settings: {
    react: {
      version: 'detect'  // 自动检测 React 版本
    }
  },
  
  rules: {
    // React 特定规则
    'react/react-in-jsx-scope': 'error',  // 在 JSX 中需要 React 作用域
    'react/prop-types': 'warn',          // PropTypes 检查
    'react/self-closing-comp': 'error',   // 自闭合标签
    'react/jsx-fragments': ['error', 'syntax'], // JSX 片段语法
    'react/jsx-props-no-spreading': 'off', // 允许传播 props
    'react/jsx-max-props-per-line': ['error', { 'maximum': 1, 'when': 'multiline' }], // 每行最多一个 prop
    'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], // 多行多 prop 时第一个 prop 换行
    'react/jsx-closing-bracket-location': ['error', 'line-aligned'] // 闭合括号位置
  }
};

2. Import 插件

javascript
// .eslintrc.js
module.exports = {
  plugins: [
    'import'
  ],
  
  extends: [
    'plugin:import/errors',
    'plugin:import/warnings'
  ],
  
  settings: {
    'import/resolver': {
      // 配置模块解析器
      node: {
        paths: ['src'],
        extensions: ['.js', '.jsx', '.ts', '.tsx']
      }
    }
  },
  
  rules: {
    // Import 相关规则
    'import/no-unresolved': 'error',           // 检查模块是否可解析
    'import/named': 'error',                   // 检查命名导入
    'import/default': 'error',                 // 检查默认导入
    'import/namespace': 'error',               // 检查命名空间导入
    'import/no-cycle': 'error',                // 检查循环依赖
    'import/no-unused-modules': 'warn',        // 检查未使用的模块
    'import/no-duplicates': 'error',           // 检查重复导入
    'import/order': [                          // 导入顺序
      'error',
      {
        groups: [
          'builtin',      // 内置模块
          'external',     // 外部模块
          'internal',     // 内部模块
          'parent',       // 父目录
          'sibling',      // 同级目录
          'index'         // 索引文件
        ],
        'newlines-between': 'always',
        alphabetize: {
          order: 'asc',   // 升序排列
          caseInsensitive: true
        }
      }
    ]
  }
};

3. JSX Accessibility 插件

javascript
// .eslintrc.js
module.exports = {
  plugins: [
    'jsx-a11y'
  ],
  
  extends: [
    'plugin:jsx-a11y/recommended'
  ],
  
  rules: {
    'jsx-a11y/alt-text': 'error',                    // 图片需要 alt 属性
    'jsx-a11y/anchor-has-content': 'error',          // anchor 标签需要内容
    'jsx-a11y/anchor-is-valid': 'error',             // anchor 标签有效性
    'jsx-a11y/click-events-have-key-events': 'error', // 点击事件需要键盘事件
    'jsx-a11y/control-has-associated-label': 'warn',  // 控件需要关联标签
    'jsx-a11y/label-has-associated-control': 'error', // label 需要关联控件
    'jsx-a11y/media-has-caption': 'error',           // 媒体需要字幕
    'jsx-a11y/mouse-events-have-key-events': 'error', // 鼠标事件需要键盘事件
    'jsx-a11y/no-autofocus': 'error',                // 禁止使用 autofocus
    'jsx-a11y/no-noninteractive-element-interactions': 'error', // 非交互元素交互事件
    'jsx-a11y/no-static-element-interactions': 'error' // 静态元素交互事件
  }
};

4. TypeScript 插件

javascript
// .eslintrc.js
module.exports = {
  plugins: [
    '@typescript-eslint'
  ],
  
  extends: [
    '@typescript-eslint/recommended'
  ],
  
  parser: '@typescript-eslint/parser',
  
  rules: {
    // TypeScript 特定规则
    '@typescript-eslint/explicit-function-return-type': 'warn', // 显式函数返回类型
    '@typescript-eslint/explicit-module-boundary-types': 'warn', // 显式模块边界类型
    '@typescript-eslint/no-explicit-any': 'warn',               // 禁止使用 any
    '@typescript-eslint/no-unused-vars': 'error',               // 禁止未使用的变量
    '@typescript-eslint/no-inferrable-types': 'warn',          // 禁止可推断的类型定义
    '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], // 类型定义一致性
    '@typescript-eslint/prefer-const': 'error',                 // 优先使用 const
    '@typescript-eslint/no-var-requires': 'error',              // 禁止使用 require
    '@typescript-eslint/explicit-member-accessibility': ['error', { 'accessibility': 'no-public' }], // 成员可访问性
    '@typescript-eslint/camelcase': 'off',                      // 关闭 camelcase(被 naming-convention 替代)
    '@typescript-eslint/naming-convention': [                   // 命名约定
      'error',
      {
        selector: 'variableLike',
        format: ['camelCase', 'UPPER_CASE']
      },
      {
        selector: 'typeLike',
        format: ['PascalCase']
      }
    ]
  }
};

5. Jest 插件

javascript
// .eslintrc.js
module.exports = {
  plugins: [
    'jest'
  ],
  
  extends: [
    'plugin:jest/recommended'
  ],
  
  env: {
    'jest/globals': true  // 启用 Jest 全局变量
  },
  
  rules: {
    'jest/expect-expect': 'off',           // 不要求每个测试都有 expect
    'jest/no-disabled-tests': 'warn',      // 禁止禁用测试
    'jest/no-focused-tests': 'error',      // 禁止聚焦测试
    'jest/no-identical-title': 'error',    // 禁止相同的标题
    'jest/prefer-to-have-length': 'warn',  // 优先使用 toHaveLength
    'jest/valid-expect': 'error',          // 有效的 expect
    'jest/expect-expect': 'off'            // 不强制要求 expect 断言
  }
};

创建自定义插件

插件结构

eslint-plugin-my-awesome-plugin/
├── package.json
├── lib/
│   ├── rules/
│   │   ├── my-awesome-rule.js
│   │   └── another-rule.js
│   └── configs/
│       ├── recommended.js
│       └── all.js

自定义规则示例

javascript
// lib/rules/my-awesome-rule.js
module.exports = {
  meta: {
    type: 'problem',  // 'problem', 'suggestion', or 'layout'
    docs: {
      description: '描述规则的目的',
      category: 'Best Practices',
      recommended: false
    },
    fixable: 'code',  // 或 'whitespace', 'code', or null
    schema: [         // 规则选项的 JSON Schema
      {
        type: 'object',
        properties: {
          option1: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ]
  },
  
  create: function(context) {
    return {
      // AST 节点访问器
      VariableDeclaration: function(node) {
        if (node.kind !== 'const') {
          context.report({
            node: node,
            message: '应该使用 const 而不是 let 或 var',
            fix: function(fixer) {
              // 自动修复
              return fixer.replaceText(node.declarations[0].id, 'const');
            }
          });
        }
      }
    };
  }
};

插件配置示例

javascript
// lib/configs/recommended.js
module.exports = {
  plugins: [
    'my-awesome-plugin'
  ],
  rules: {
    'my-awesome-plugin/my-awesome-rule': 'error',
    'my-awesome-plugin/another-rule': 'warn'
  }
};

插件入口文件

javascript
// index.js
module.exports = {
  rules: {
    'my-awesome-rule': require('./lib/rules/my-awesome-rule'),
    'another-rule': require('./lib/rules/another-rule')
  },
  configs: {
    recommended: require('./lib/configs/recommended'),
    all: require('./lib/configs/all')
  }
};

插件最佳实践

1. 性能考虑

javascript
module.exports = {
  rules: {
    // 对性能影响较小的规则
    'no-console': 'warn',
    'semi': 'error',
    
    // 谨慎使用对性能影响较大的规则
    'complexity': ['warn', 10],  // 圈复杂度检查
    'max-depth': ['warn', 4]     // 嵌套深度检查
  },
  
  // 使用 overrides 为不同文件类型设置不同规则
  overrides: [
    {
      files: ['*.test.js', '*.spec.js'],
      rules: {
        'no-unused-vars': 'off',  // 测试文件中可适当放宽规则
        'max-nested-callbacks': 'off'
      }
    }
  ]
};

2. 规则命名约定

javascript
// 好的规则命名
'no-implicit-coercion': 'error',  // 否定前缀表示禁止某种行为
'prefer-template': 'error',       // prefer 前缀表示推荐某种做法
'max-len': ['error', 120],        // max 前缀表示最大限制

// 配置插件时的命名空间
'react/jsx-uses-react': 'error',  // 插件名/规则名
'import/order': 'error'           // 插件名/规则名

常见插件配置

React + TypeScript + Hooks 配置

javascript
module.exports = {
  plugins: [
    'react',
    '@typescript-eslint',
    'react-hooks'
  ],
  
  extends: [
    'plugin:react/recommended',
    '@typescript-eslint/recommended',
    'plugin:react-hooks/recommended'
  ],
  
  settings: {
    react: {
      version: 'detect'
    }
  },
  
  rules: {
    'react-hooks/rules-of-hooks': 'error',      // 检查 Hook 规则
    'react-hooks/exhaustive-deps': 'warn',      // 检查 effect 依赖
    'react/react-in-jsx-scope': 'off',          // React 17+ 不需要导入
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off'
  }
};

Prettier 集成

javascript
module.exports = {
  plugins: [
    'prettier'
  ],
  
  extends: [
    'prettier'  // 关闭与 Prettier 冲突的规则
  ],
  
  rules: {
    'prettier/prettier': 'error'  // 运行 Prettier 格式化
  }
};

小结

ESLint 插件系统提供了强大的扩展能力,通过使用和创建插件,我们可以根据项目需求定制代码检查规则。选择合适的插件并正确配置是保证代码质量的关键。