历史(History)
怎么使用 (How to use)
- .eslintrc配置说明
如何写一个规则(Write a rule)
- AST
- code path analysis
- Eslint工作流程 简单聊下mpx的eslint
如何写一个规则
让我们看看有哪些规则
有很多规则,眼花缭乱,文档中一些前面打✅的是推荐的,没打的就是根据需求定制,有🔧符号的是可以修复的那种,可以通过--fix或者在编辑器中提示修复选项中修复 让我们看一个例子"no-debugger": "error"
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow the use of `debugger`",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-debugger"
},
fixable: null,
schema: [],
messages: {
unexpected: "Unexpected 'debugger' statement."
}
},
create(context) {
return {
DebuggerStatement(node) {
context.report({
node,
messageId: "unexpected"
});
}
};
}
};
具体字段的解释在官网中解释的很详细
简单来说meta主要是规则的基本配置,描述这个规则,create中是具体实现,这个例子是当代码中出现debugger时会提示报错。
重点讲一下create函数 。 拿上面例子看,create中需要知道的几个元素:
- 参数context
- 当遇到debugger时触发DebuggerStatement
- 返回AST节点node
context
顾名思义,context就是一个对象包含与规则上下文相关的信息,我总结了一些常用的api,比如
- context.report: 报告错误的代码,比较容易理解
- context.options:配置中的一些选项,比如
{
"quotes": ["error", "double"]
}
// context.options[0] === "double" true
-
context.getScope: 获取当前规则的作用域,它提供一些当前作用域的一些内容,比如:
- type: 当前作用域类型
- isStrict: 是不是strict模块
- upper: 父级作用域
- childScopes: 子级作用域
- variables: 声明的一些变量
- through:由无法解析的变量组成的数组
- references: 此范围所有引用的数组
- ...
-
context.getSourceCode: 是获取源代码的api,返回一个SourceCode对象,可以用这个对象返回节点的源码,或者返回节点的第一个或者最后一个token等等对节点文本的查询判断操作。
DebuggerStatement
是AST的一种节点类型,常用的类型
node
是AST的一个节点,具体可以看一下
具体的AST可以尝试在这里写一段代码,看下AST长什么样子
code path analysis
简单来说就是解析代码的一个执行路径,加入一些钩子函数,帮助我们在这个阶段做一些事情。
if (a && b) {
foo();
}
bar();
我们分析一下上述代码的可能执行路径:
- 如果 a 为真 - 检测 b 是否为 真
- 如果 b 为真 — 执行 foo() — 执行 bar()
- 如果 b 非真 — 执行 bar()
- 如果 a 非真,执行 bar() 具体的图示:
这是一个包含AST具体类型的codepath,整体可以看作一个CodePath,然后由四个CodePathSegment组成,Eslint抽象了五个钩子
- onCodePathStart
- onCodePathEnd
- onCodePathSegmentStart
- onCodePathSegmentEnd
- onCodePathSegmentLoop 根据函数名称容易想到:整体的开始,结束。各自模块的开始,结束,还有一个就是循环语句 上诉代码的执行顺序应该是
onCodePathStart
onCodePathSegmentStart
onCodePathSegmentEnd
onCodePathSegmentStart
onCodePathSegmentEnd
onCodePathSegmentStart
onCodePathSegmentEnd
onCodePathSegmentStart
onCodePathSegmentEnd
onCodePathEnd
Eslint的工作流程
这张是整体的一个流程,eslint也是这种插件的架构,parser也可以替换,规则可以自己生成配置,通过多个plugin的作用,生成代码的问题列表作用于ide,架构极具灵活性。下面是更深入的探究。
上图是babel对ast的应用,下图是eslint对ast的处理
babel是把code解析成ast然后调整ast产出期望的code
eslint则是解析成ast后,然后把eslint拍平并且产生了对称的结构,具体是对这个ast进行深度优先遍历,在遍历节点的开始存下node,结束再存一次,生成了一个这样的数组。
然后再遍历这个ast的数组,如果你的规则中写了相关ast节点的处理,则告知你这个方法执行。比如:code中有debugger,ast中就存在DebuggerStatement,你的rule里写了DebuggerStatement,在遍历数组的时候,它遇到了DebuggerStatement,则告知你执行你的DebuggerStatement方法 这里我就产生一些疑问?
- 为什么要拍平ast?
- 为什么要复制一些节点,按照对称的结构存储?
为什么要拍平ast?
eslint明明可以在深度优先遍历ast的时候做告知规则的处理,为什么要构建一个这样的数组,再遍历数组去做呢?这样做不是性能有所缺失嘛。
的确如此,我觉得代码的作者放弃了一些性能,保证了代码的可维护性,就是让遍历ast成为一个单一职责的函数,让具体的规则处理在外部执行。并且遍历ast这个函数在单元测试也得到了复用
为什么要复制一些节点,按照对称的结构存储?
这个则是让规则开发者对静态代码充分的控制,也可以理解为节点的结束回调,可以看到每个ast数组里有两个ast节点,拿DebuggerStatement举例:
- 第一个节点 DebuggerStatement() { // todo }
- 第二个节点 DebuggerStatement:exit() { // todo } 理解了上述我再扩展和简述下流程,如下图:
我们从下往上看,eslint的底层是acorn这个库,最早的解析器是esprima,后来用espree。
- acorn:生成ast的工具库
- espree:其实就是包装了acorn提供了和esprima一样的接口函数。
- esquery: 类似jquery,对ast节点和规则的selector进行查找匹配,如果匹配成功则执行相应的规则函数
- lintingProblems: eslint处理后得到的问题列表
- eslint-visitor-keys: ast遍历的时候用于找到可遍历的子节点,就比如遍历tree的时候访问的是leftNode或者rightNode,这个库提供ast的节点的可遍历子节点map
{
"FunctionDeclaration": [
"id",
"params",
"body"
],
// ...
}
从左侧看,.eslintrc中提供的是parser,默认是解析js的espree,当然可以改成解析vue的vue-eslint-parser plugin是相关语言框架的插件,里面可以配置自己的parser,rules等等,并且还得配置processors识别相关的文件比如.vue.mpx等等 rules是具体配置的规则,具体的规则需要在相应的parser下才能生效,如果你配置了个espree,那vue的规则是不会解析js的ast的。 中间内容也是最重要的部分,
- 首先eslint整合了规则,把extend的,插件里的规则合并在一起。
- 然后根据优先级关系选择解析器,传入code解析出ast,提供给runrules方法使用
- 前面做的是准备工作,runrules是核心规则对code的处理.
- 首先遍历ast,生成一个回文的ast数组,也即是ast每个节点存在两个
- 然后给规则里对ast的处理函数注册监听,达到代码的ast有相应的规则则发送告知处理
- 然后遍历ast数组的节点,找到相应的规则,执行规则拿到处理结果
- 最后把结果也就是有问题的代码返回给调用方
mpx的Eslint
当然eslint不只是针对js做处理,我觉得只要你的code是有规律的,能生成类似ast的结构都可以做成一个解析器,然后来制定一些规则规范代码。
mpx是滴滴出品的一款小程序框架,不同于业内大部分小程序框架将web MVVM框架迁移到小程序中运行的做法,以小程序原生的语法和技术能力为基础,借鉴参考了主流的web技术设计对其进行了扩展与增强,并在此技术上实现了以微信增强语法为base的同构跨平台输出,详情戳
mpx的eslint大部分都是仿照vue的eslint做的,相比vue,mpx有四个模块,template,js,styles,json,比vue多了json模块,解决这个json模块还遇到了很多问题,比如多了一个script标签,vue的开始设计时就完全没考虑可能存在两个script标签,以至于我们会展示两条相同的错误。
eslint主要作用于三个地方吧,
- mpx的vscode插件
- .eslintrc,直接配置的形式,走的是eslint的vscode插件
- webpack配置中要做个loader(暂时没深入研究)
mpx的vscode插件
如上文我所说的,分为四个模块,每个模块分别解析,不是整体的解析
- template: 这个用的是eslint-plugin-mpx专门的eslint插件,还有专属的解析器mpx-eslint-parser,插件的主要内容是,一些关于template中小程序标签的规则编写,以及识别.mpx文件。解析器是解析template中的标签,生成一个标签的ast,用于插件编写规则
- js:这个用的是typescript中提供的语言服务解析出来,不仅可以对js的代码解析,还可以对ts的代码解析
- styles: 这个是vscode官方提供了一些比如less,css的lint库
- json:这个是我们找了一个eslint-plugin-jsonc库来解决json的eslint问题
eslint的vscode插件
当然可以在.eslintrc中配置eslint-plugin-mpx,相比于mpx的vscode中的优势在于可以配置很多规则,可以随意下掉规则,mpx的vscode中的规则是固定的必要的规则,比较死板,当然可以通过配置关闭mpx的vscode的eslint。
官网 eslint.cn/