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