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,然后我们就可以像使用第三方包那样引用了。