Eslint是我们每天都会用到的一个工具,我们会使用它的cli或者api来做代码错误检查和格式检查,有时也会写一些规则来做自定义检查和修复。虽然我们每天都在使用它,但我们很少了解它是如何实现的。了解Eslint的实现原理,可以帮助我们更好的使用它,更好的编写一些插件。因此,在本文中,我们将通过源码来探究Eslint的实现原理。LinterLinter是eslint的核心类。它提供了这些api:verify//checkverifyAndFix//checkandfixgetSourceCode//getASTdefineParser//defineParserdefineRule//defineRulegetRules//getallRuleSourceCode引用AST(抽象语法树),Parser将源代码字符串解析成AST,而Rule是我们配置的用于检查AST的规则。这些API相对容易理解。Linter的主要功能在verify和verifyAndFix中实现。当命令行指定--fix或者配置文件指定fix:true时,会调用verifyAndFix对代码进行检查修复,否则会调用verify进行检查。如何验证和修复?这是eslint的核心部分:确定解析器,我们知道Eslint的规则是根据AST来检查的,所以我们首先要将源码解析成AST。而且eslint的解析器也是可以切换的,需要先搞清楚使用什么解析器:默认是Eslint自带的espree,也可以通过配置切换到其他解析器,比如@eslint/babel-parser,@typescript/eslint-parser等。下面是resolveparser的逻辑:解析器确定后,调用parse方法。parseintoSourceCodeparser的parse方法会将源码解析成AST,而在eslint中,SourceCode是用来封装AST的。稍后会看到SourceCode指的是AST。使用AST,您可以调用规则来检查AST。调用规则检查SourceCode。获取lintingProblemsparse后,您将调用runRules方法来检查AST。返回的结果是problems,即错误信息是什么,如何修复。那么runRules是如何运行规则的呢?rule的实现如下,就是注册什么检查什么AST,和babel插件很像。runRules会遍历AST,遇到不同的AST就会发出不同的事件。规则中处理什么AST,就会监听什么事件,这样通过事件监听,可以在遍历AST的过程中执行不同的规则。注册监听器:遍历AST,发出不同的事件,触发监听器:这样,遍历一次AST后,所有的规则都会被调用,这就是规则的运行机制。另外在遍历过程中会传入context,可以在rule中获取,比如scope,settings等。还有ruleContext,可以在调用ASTlistener时获取:rule报错通过reportapi,以便收集和打印所有错误。这是什么问题?Lintingproblemlint问题是检查的结果,即从哪一行(line)哪一列(column)到哪一行(endLine)哪一列(endColumn),有什么错误(message)。还有就是如何修复(fix)。fix其实就是从哪个下标到哪个下标(范围),替换什么文字。为什么要修复这样一个范围返回和文本的结构?因为它的实现是一个简单的字符串替换。通过字符串替换自动修复在遍历AST,调用所有规则,收集linting问题后,就可以进行修复了。fix部分相关源码如下:即verify校验,然后根据fix信息自动修复。fix其实就是字符串替换:可能有同学注意到了,为什么要加一个while循环来进行字符串替换呢?因为多次修复之间的范围,也就是替换的范围,可能会重叠。如果有重叠,就放在下次修复,这样while循环最多修复10次。如果还有fix没修好,就不修了。这就是fix的实现原理,通过字符串替换实现。如果有重叠,将在循环中修复。preprocess和postprocess的核心verify和fix流程其实就是上面这些,但是Eslint也支持一些前后处理。也就是前处理和后处理,也是在插件中定义的。module.exports={processors:{".txt":{preprocess:function(text,filename){return[//returnanarrayofcodeblockstolint{text:code1,filename:"0.js"},{text:code2,filename:"1.js"},];},postprocess:function(messages,filename){return[].concat(...messages);}}}};前面的处理是将非js文件一个一个的js文件解析出来,这个很像webpack的loader,让Eslint对非JS文件进行lint处理。后续处理呢?一定是在处理问题,也就是消息。可以过滤掉一些消息,或者可以进行一些修改。预处理和后处理是如何实现的?这个比较简单,verify前后调用就行了。使用注释指令来过滤掉一些问题。我们知道eslint也支持通过注释进行配置,比如/*eslint-disable*//*eslint-enable*/。那么它是如何实现的呢?注释的配置是通过扫描AST来收集所有配置的。这个配置称为commentDirective,即Eslint是在哪一行或哪一列生效。然后在verify的最后,将收集到的linting问题过滤一次即可。以上就是Eslint的实现原理:Eslint和CLIEngine类Linter实现了核心功能,上面我们已经介绍过了,但是在命令行场景下,需要处理一些命令行参数,所以需要再封装一层CLIEngine,用于读写文件,解析命令行参数。它有executeOnFiles、executeOnText等API,都是基于Linter类的上层封装。不过CLIEngine并没有直接暴露出来,而是包裹了一层EsLint类,只是一个比较好用的门面,隐藏了一些不相关的信息。再来看看eslint最终暴露的API:Linter是直接lints文本的核心类ESLint处理配置,读写文件等,然后调用Linter进行lint(CLIEngine,中间层,没有暴露)SourceCode是封装了AST的RuleTester,是一些规则测试的API。综上所述,我们通过源码明确了eslint的实现原理:ESLint的核心类是Linter,分为以下几个步骤:预处理,将非js文本处理成js,确定解析器(默认是espree),调用解析器,将源码解析成SourceCode(ast)调用规则,检查SourceCode,返回linting问题,扫描注释中的指令,过滤问题postprocess,一次性处理问题。基于字符串替换,实现自动修复。除了核心的Linter类,还有用于处理配置和读写文件的CLIEngine类,以及最终暴露的Eslint类。这就是Eslint的实现原理,其实很简单:基于AST的check,基于string的fix,前后有pre和post过程,支持注解配置和过滤一些问题。搞清楚了这些,就算在源码层面掌握了Eslint。
