嘿,各位代码界的弄潮儿!今天咱们来聊点刺激的——JS ESLint 插件开发,玩转自定义代码规范,让你的代码像艺术品一样优雅!
一、啥是 ESLint?为啥要搞插件?
简单来说,ESLint 就是个代码质量检测工具,能帮你找出代码里的潜在 Bug、不规范写法,让你少加班,多摸鱼(划掉)… 提升效率!
为啥要开发插件呢?因为 ESLint 内置的规则再强大,也满足不了所有人的需求。比如,你公司有自己的一套命名规范,或者项目里有一些特殊的约定,就需要自定义规则来约束。
举个例子,假设你的团队喜欢用 _
开头的变量表示私有变量,但 ESLint 默认是不允许的。这时候,你就可以写个插件,告诉 ESLint:“嘿,哥们儿,见到 _
开头的变量别大惊小怪,这是我们内部的规矩!”
二、AST:代码的“X 光片”
要搞 ESLint 插件,就得先了解 AST(Abstract Syntax Tree,抽象语法树)。你可以把它想象成代码的“X 光片”,它把代码结构拆解成一棵树,每个节点都代表一个语法单元(比如变量声明、函数调用、表达式等等)。
ESLint 插件的核心就是分析 AST,找到不符合规范的节点,然后发出警告或错误。
来个简单的例子:
const a = 1 + 2;
这行代码的 AST 大概长这样(简化版):
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "a"
},
"init": {
"type": "BinaryExpression",
"operator": "+",
"left": {
"type": "Literal",
"value": 1
},
"right": {
"type": "Literal",
"value": 2
}
}
}
],
"kind": "const"
}
可以看到,const a = 1 + 2;
被拆解成了 VariableDeclaration
(变量声明)、VariableDeclarator
(变量声明符)、Identifier
(标识符,也就是变量名)、BinaryExpression
(二元表达式)、Literal
(字面量,也就是数字 1 和 2)等等节点。
三、插件开发:手把手教你撸一个
咱们现在就来撸一个简单的 ESLint 插件,实现一个规则:禁止使用 console.log
。
1. 初始化项目
首先,新建一个文件夹,比如 eslint-plugin-no-console-log
,然后在里面初始化一个 npm 项目:
mkdir eslint-plugin-no-console-log
cd eslint-plugin-no-console-log
npm init -y
2. 安装 ESLint
npm install eslint --save-dev
3. 创建规则文件
在项目根目录下创建一个 rules
文件夹,然后在里面创建一个 no-console-log.js
文件。这个文件就是我们自定义规则的实现。
rules/no-console-log.js
的内容如下:
module.exports = {
meta: {
type: 'problem', // 规则类型:problem, suggestion, layout
docs: {
description: '禁止使用 console.log', // 规则描述
category: 'Possible Errors', // 规则分类
recommended: 'error', // 推荐级别:off, warn, error
url: null, // 规则文档地址
},
fixable: null, // 是否可自动修复:null, 'code'
schema: [], // 规则配置项
messages: {
noConsoleLog: '禁止使用 console.log', // 错误提示信息
},
},
create: function (context) {
return {
CallExpression: function (node) {
if (node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'console' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'log') {
context.report({
node: node,
messageId: 'noConsoleLog',
});
}
},
};
},
};
代码解释:
meta
:定义规则的元数据,包括类型、描述、推荐级别、配置项等等。create
:核心函数,接收一个context
对象,用于报告错误和访问 AST。CallExpression
:ESLint 会遍历 AST,当遇到CallExpression
类型的节点时,就会调用这个函数。node
:当前遍历到的 AST 节点。context.report
:用于报告错误,接收一个对象,包含节点信息和错误提示信息。
4. 创建插件入口文件
在项目根目录下创建一个 index.js
文件,作为插件的入口。
index.js
的内容如下:
module.exports = {
rules: {
'no-console-log': require('./rules/no-console-log'),
},
configs: {
recommended: {
rules: {
'no-console-log/no-console-log': 'error',
},
},
},
};
代码解释:
rules
:定义插件包含的规则,键是规则名称,值是规则的实现。configs
:定义插件的预设配置,比如recommended
,可以方便用户直接使用推荐的规则集。
5. 配置 ESLint
在项目根目录下创建一个 .eslintrc.js
文件,配置 ESLint 使用我们的插件。
.eslintrc.js
的内容如下:
module.exports = {
plugins: [
'no-console-log', // 插件名称,通常是 npm 包名
],
extends: [
'eslint:recommended', // 继承 ESLint 推荐的规则
'plugin:no-console-log/recommended', // 使用插件的 recommended 配置
],
rules: {
// 在这里可以覆盖或添加其他规则
},
};
6. 测试规则
创建一个 test.js
文件,写入包含 console.log
的代码:
console.log('Hello, world!');
然后在命令行运行 ESLint:
npx eslint test.js
如果一切正常,你应该会看到类似这样的错误提示:
test.js
1:1 error 禁止使用 console.log no-console-log/no-console-log
✖ 1 problem (1 error, 0 warnings)
四、规则进阶:更复杂的 AST 分析
上面的例子只是个简单的入门,实际开发中,你可能需要分析更复杂的 AST 结构,才能实现更精细的规则。
比如,你想禁止使用 alert
函数,但只在 if
语句中使用时才禁止。这时候,你需要分析 alert
函数的父节点是否是 IfStatement
。
module.exports = {
meta: {
type: 'problem',
docs: {
description: '禁止在 if 语句中使用 alert',
category: 'Possible Errors',
recommended: 'error',
},
fixable: null,
schema: [],
messages: {
noAlertInIf: '禁止在 if 语句中使用 alert',
},
},
create: function (context) {
return {
CallExpression: function (node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'alert') {
// 向上查找父节点
let parent = node.parent;
while (parent) {
if (parent.type === 'IfStatement') {
context.report({
node: node,
messageId: 'noAlertInIf',
});
break;
}
parent = parent.parent;
}
}
},
};
},
};
代码解释:
node.parent
:访问当前节点的父节点。while (parent)
:循环向上查找父节点,直到找到IfStatement
类型的节点。
五、可配置规则:让用户自定义行为
有时候,你可能需要让用户自定义规则的行为,比如允许用户配置哪些变量名可以以 _
开头。这时候,你需要使用 schema
来定义规则的配置项。
module.exports = {
meta: {
type: 'problem',
docs: {
description: '允许使用 _ 开头的变量名',
category: 'Stylistic Issues',
recommended: 'off',
},
fixable: null,
schema: [
{
type: 'array',
items: {
type: 'string',
},
minItems: 0,
uniqueItems: true,
description: '允许使用 _ 开头的变量名列表',
},
],
messages: {
invalidName: '变量名 {{name}} 不符合规范',
},
},
create: function (context) {
// 获取配置项
const allowedNames = context.options[0] || [];
return {
Identifier: function (node) {
if (node.name.startsWith('_') && !allowedNames.includes(node.name)) {
context.report({
node: node,
messageId: 'invalidName',
data: {
name: node.name,
},
});
}
},
};
},
};
代码解释:
schema
:定义规则的配置项,这里定义了一个数组类型的配置项,允许用户传入一个字符串列表。context.options
:访问用户配置的选项。data
:可以在错误提示信息中使用模板字符串,将变量名传递给用户。
在 .eslintrc.js
中配置规则:
module.exports = {
plugins: [
'my-custom-rules',
],
extends: [
'eslint:recommended',
],
rules: {
'my-custom-rules/allow-underscore-names': [
'error',
['_privateVariable', '_internalFunction'], // 允许使用 _privateVariable 和 _internalFunction
],
},
};
六、自动修复:让代码自动变美
ESLint 还可以自动修复一些简单的代码风格问题,比如自动添加分号、自动调整缩进等等。要实现自动修复,需要在 meta
中设置 fixable: 'code'
,并在 context.report
中提供 fix
函数。
module.exports = {
meta: {
type: 'layout',
docs: {
description: '强制在语句末尾添加分号',
category: 'Stylistic Issues',
recommended: 'warn',
},
fixable: 'code',
schema: [],
messages: {
missingSemicolon: '语句末尾缺少分号',
},
},
create: function (context) {
return {
ExpressionStatement: function (node) {
if (node.expression.type !== 'Literal' && node.range[1] !== ';') {
context.report({
node: node,
messageId: 'missingSemicolon',
fix: function (fixer) {
return fixer.insertTextAfter(node, ';');
},
});
}
},
};
},
};
代码解释:
fixable: 'code'
:表示该规则支持自动修复。fix
:一个函数,接收一个fixer
对象,用于生成修复操作。fixer.insertTextAfter
:在指定节点之后插入文本。
七、发布插件:让更多人受益
当你完成一个有用的 ESLint 插件后,可以把它发布到 npm 上,让更多人使用。
-
修改
package.json
name
:插件名称,必须以eslint-plugin-
开头。main
:插件入口文件,通常是index.js
。keywords
:关键词,方便用户搜索。files
:指定要发布的文件,通常包括index.js
、rules
文件夹等等。peerDependencies
:声明对 ESLint 的依赖。
{ "name": "eslint-plugin-no-console-log", "version": "1.0.0", "description": "禁止使用 console.log 的 ESLint 插件", "main": "index.js", "keywords": [ "eslint", "eslintplugin", "console", "log" ], "files": [ "index.js", "rules" ], "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" }, "author": "Your Name", "license": "MIT" }
-
登录 npm
npm login
-
发布插件
npm publish
八、总结:代码规范,从我做起
ESLint 插件开发是一个充满乐趣和挑战的过程,它可以让你深入了解代码的结构和规范,提升你的编程水平。希望今天的讲座能帮助你入门 ESLint 插件开发,写出更优雅、更规范的代码!记住,代码规范不是束缚,而是提升效率、减少 Bug 的利器!
表格总结:
步骤 | 描述 | 命令/代码示例 |
---|---|---|
1. 初始化项目 | 创建项目文件夹并初始化 npm 项目。 | mkdir eslint-plugin-my-rule cd eslint-plugin-my-rule npm init -y |
2. 安装 ESLint | 安装 ESLint 作为开发依赖。 | npm install eslint --save-dev |
3. 创建规则文件 | 创建规则的实现文件 (例如: rules/my-rule.js ),定义规则的 meta 和 create 方法。 |
module.exports = { meta: { ... }, create: function(context) { ... } } |
4. 创建插件入口文件 | 创建插件的入口文件 (index.js ),导出规则。 |
module.exports = { rules: { 'my-rule': require('./rules/my-rule') } } |
5. 配置 ESLint | 在 .eslintrc.js 中配置 ESLint 使用插件和规则。 |
plugins: ['my-plugin'], rules: { 'my-plugin/my-rule': 'error' } |
6. 测试规则 | 创建测试文件并运行 ESLint。 | npx eslint test.js |
7. 规则进阶 | 学习更复杂的 AST 分析方法,例如访问父节点。 | node.parent |
8. 可配置规则 | 使用 schema 定义规则的配置项,让用户自定义行为。 |
meta: { schema: [ { type: 'string' } ] }, create: function(context) { const option = context.options[0]; ... } |
9. 自动修复 | 实现自动修复功能,使用 fixer 对象生成修复操作。 |
meta: { fixable: 'code' }, create: function(context) { fix: function(fixer) { return fixer.insertTextAfter(node, ';'); } } |
10. 发布插件 | 修改 package.json 并发布插件到 npm。 |
npm login npm publish |
好了,今天的分享就到这里。希望大家都能成为代码规范的守护者!下次再见!