Eslint
前言
在我们代码中,经常会有和后端协商的常量,比如是‘MALE’,‘FEMALE’,这样还好理解,看上去知道分别对应的意思是什么,但如果给你的是两个数字0和1,难免会造成混淆,让我们的代码存在风险。我推荐的做法是在前端自己定义枚举值。
例如:
|
对比纯数字的写法,这种枚举往往更能提高我们代码的质量。除了数字,我觉得所有的字面量都有必要使用枚举类型,有两点我觉得也很重要。
- 便于维护:在迭代初期,我们无需等后端定义好值,我们直接前端先自己用一个假的值定义枚举进行开发,后面只需要修改枚举的值即可,而不用到处找使用的常量去修改,也方便如果后期维护后端需要修改值,我们直接修改我们定义的枚举值即可。
- 利用IDEA提示:我们可以更好的利用IDEA的提示,如上面写好注释,在使用的地方可以看到具体代表的是什么意思,也可以利用好IDEA的补全和校验,防止写错字面量。
在最近参与到一个广告投放工具项目里,对接巨量快手腾讯三个平台,而后端之前定义的全是数字,23,27,22,三个可能都还好,后面再对接几个平台,多几个数字,确定能记得清哪个是哪个?因此我定义好了枚举,并且希望通过Eslint去强制使用枚举而不是字面量,约束自己也约束团队,减少bug以及提高代码的质量。
认识Eslint
我们经常在项目中使用Eslint,常用的配置在网上也很容易找到,Eslint的原理是怎么样呢
ESLint 的工作原理可以归纳为以下步骤:
解析代码:将源代码转换为 AST。
ESLint 使用解析器将代码解析成抽象语法树(AST)。AST 是一种树状结构,表示源代码的语法结构
应用规则:遍历 AST,检查各个节点是否符合规则。
ESLint 包含一系列内置规则,并支持用户定义的自定义规则。这些规则会对 AST 进行检查,寻找潜在问题。当 ESLint 解析完代码并生成 AST 后,它会遍历这个 AST,检查每个节点。根据启用的规则,ESLint 会执行相应的检查。
报告问题:生成并输出问题报告。
ESLint 在检查完所有规则后,会生成一个结果报告。这个报告可以是文本格式、JSON 格式等,具体取决于配置。
扩展功能:支持插件和共享配置。
ESLint 支持通过插件来扩展功能。插件可以包含自定义规则、共享配置和额外的解析器。
ESLint 可以与多种开发工具集成,编辑器,构建工具,CI/CD等,共同维护整个项目的代码质量。
AST的类型
上面说到Eslint的规则主要基于AST,利用源代码各个结构部分的节点类型进行规则的匹配和校验,那么我们要自定义一个规则,了解一下AST就很有必要。
以下是常见的AST类型
Program
- 顶级节点,表示整个程序(源文件)。
ExpressionStatement
表示单个表达式的语句。
属性:
expression,一个表达表达式的节点。foo();这里的
foo();是一个ExpressionStatement,它包含一个函数调用表达式。
Literal
表示字面量,如字符串、数字、布尔值等。
属性:
value,字面量的值。42
"hello"
true每个字面量在 AST 中是一个
Literal节点。
Identifier
表示标识符(变量名、函数名、属性名等)。
属性:
name,标识符的名称。const foo = 42;这里
foo是一个Identifier节点。
VariableDeclaration
表示变量声明(
var、let、const)。属性:
declarations,一个包含所有声明的数组。kind,表示使用的声明类型(var、let、const)。
const foo = 42;
let bar;这里
const foo = 42;是一个VariableDeclaration节点。
VariableDeclarator
表示具体的变量声明。
属性:
id,表示被声明的变量(是一个Identifier节点)。init,表示变量的初始化值(如果有)。
const foo = 42;这里的
foo = 42是一个VariableDeclarator,id是foo,init是42。
FunctionDeclaration
表示函数声明。
属性:
id,函数的名称(是一个Identifier)。params,函数的参数列表(每个参数是一个Identifier节点)。body,函数体(是一个BlockStatement节点)。
function greet(name) {}这里整个函数声明是一个
FunctionDeclaration节点。
Property
表示对象的属性键值对。
属性:
key,表示属性名。value,表示属性值。
{ foo: 42 }这里的
foo: 42是一个Property节点。
UnaryExpression
表示一元操作符,如
!、typeof、+、-。属性:
operator,表示操作符。argument,表示操作的对象。
!flag这里的
!flag是一个UnaryExpression节点。
AssignmentExpression
表示赋值表达式。
属性:
operator,表示赋值操作符(如=、+=、-=)。left,表示被赋值的变量。right,表示赋值的值。
x = 42这里的
x = 42是一个AssignmentExpression节点。
BlockStatement
表示一组语句的块(如函数体、
if块等)。属性:
body,包含块内的语句数组。if(true){
let a = 42;
}这里的
{ let a = 42; }是一个BlockStatement节点。
ReturnStatement
表示
return语句。属性:
argument,表示返回的表达式。return 42;这里的
return 42是一个ReturnStatement。
BinaryExpression
表示二元操作符表达式,如
+、-、*、===等。属性:
left和right,表示左右操作数。operator,表示操作符(如+、===等)。
a + b这里的
a + b是一个BinaryExpression节点。
CallExpression
表示函数调用。
属性:
callee,表示被调用的对象(可能是Identifier或其他表达式)。arguments,表示调用时传入的参数数组。
foo(42);这里的
foo(42)是一个CallExpression节点。
MemberExpression
表示对象属性或方法的访问。
属性:
object,表示对象。property,表示被访问的属性或方法。
obj.foo
obj['foo']这里的
obj.foo和obj['foo']都是MemberExpression节点。
IfStatement
表示
if语句。属性:
test,表示条件表达式。consequent,表示条件为true时执行的块(是BlockStatement)。alternate,表示else的块(如果有)。
if (x > 0) {
// do something
} else {
// do something else
}这里整个
if-else语句是一个IfStatement节点。
ForStatement
表示
for循环。属性:
init,表示初始化语句。test,表示循环条件。update,表示循环后的更新表达式。body,表示循环体。
for (let i = 0; i < 10; i++) {
console.log(i);
}这里整个
for循环是一个ForStatement节点。
ArrayExpression
表示数组字面量。
属性:
elements,表示数组中的各个元素。[1, 2, 3]这里是一个
ArrayExpression节点。
ObjectExpression
表示对象字面量。
属性:
properties,表示对象中的各个Property。{ foo: 42 }这里是一个
ObjectExpression节点。
自定义Eslint
定义规则
我们直接上文件分析:
|
一个规则就是一个对象,meta主要是这个规则的信息,create是规则的具体实现。
首先了解一下meta
meta.type
表示规则的类型,可以是以下三种之一:
“problem”: 表示此规则用于发现代码中的潜在问题。
“suggestion”: 表示此规则提供建议以改善代码质量或可读性。
“layout”: 表示此规则与代码的格式或布局相关。
meta.docs
表述规则的信息,包括描述、类别和推荐。
category:
“Best Practices”: 最佳实践
“Possible Errors”: 可能的错误
“Stylistic Issues”: 风格问题
“ECMAScript 6”: ECMAScript 6 特有的规则
recommended:
表示是否推荐使用此规则。如果为true,则无需再手动启用规则。
meta.fixable
表示是否可以自动修复。
如果为true,则表示该规则可以自动修复。规则最后report时,在fixer中可以调用fixer.replaceText()方法进行修复。
|
meta.schema
表示规则的配置项。入参。类型为对象数组
。
|
create
create函数是规则的具体实现,返回一个对象,对象中包含各种AST节点的处理函数。
create函数接收一个参数context,表示当前规则的上下文。
我这个规则比较简单,因为只需要对字面量Literal这个AST节点监听即可,如果涉及与其他常用字面量混淆,可能就另外处理一下,这种情况就不太是用仅直接监听字面量了,可能需要配合其他的变量定义、操作符等等去针对特定的场景定义,我觉得大家也可以没问题就先用着,把自动fix关了,慢慢去优化规则,一开始就完美实现有点难,慢慢优化吧。。
|
使用自定义规则
Eslint是通过plugin是引入自定义规则的,所以我们需要通过将我们本地规则包装成一个plugin
在规则文件同层创建一个index.js入口文件
/eslint-rules/***.js
|
直接引入本地规则(V9.x)
注:这种方式只有在V9.x才有
local plugin:
|
或者无需配置入口文件,配置一个虚拟plugin
|
npm引入
这个直接将规则包装成插件,上传到npm,然后引入插件即可
本地npm引入
如果不想上传到npm,并且你得eslint版本不是V9.x的话,可以通过配置package.json的本地引入即可。
|
需要在路径前面添加“file:”,这样包管理器就会在本地加载放到node_modules,然后我们就可以像使用第三方包那样引用了。


