发布网友 发布时间:2天前
共1个回答
热心网友 时间:2024-10-21 16:20
不知道大家有没有用过ESLint的注释的配置方式:
/*eslint-disableno-alert,no-console*/alert('foo');console.log('bar');/*eslint-enableno-alert,no-console*///eslint-disable-next-linealert('foo');eslint支持eslint-disable、eslint-enable、eslint-disable-next-line等指定某个rule是否生效的行内配置,叫做inlineconfig。
webpack中也有这种配置方式,可以在动态引入一个模块的时候配置代码分割的方式,叫做magiccomment。
import(/*webpackChunkName:"my-chunk-name"*//*webpackMode:"lazy"*//*webpackExports:["default","named"]*/'mole');类似的,terser也有这种机制,叫做annotation,可以指定某个api是否是纯的,纯函数的话如果没用到可以直接删除。
vara=/*#__PURE__*/React.createElement("div",null);可以看到,很多库都用到了这种通过注释来配置的方式,不管是叫annotation也好、magiccomment也好,或者inlineconfig也好,都指的同一个东西。
既然是这么常见的配置方式,那么他们是怎么实现的呢?
注释中配置的实现原理我们拿eslint的inlineconfig的实现来看一下。
eslint会把源码parse成AST,然后对把AST传入一系列rule来做检查,检查结果会用formatter格式化后输出。
注释的配置是在哪一步生效的呢?
我简化了一下源码,是这样的:
verify(text){//parse源码constast=parse(text);//调用rule,拿到lint的问题constlintingProblems=runRules(ast);//通过AST拿到注释中的配置constcommentDirectives=getDirectiveComments(ast);//根据注释中的配置过滤问题returnapplyDisableDirectives(lintingProblems,commentDirectives);}可以看到,整体流程是:
把源码parse成AST
调用rule对AST做检查,拿到lint的problems
通过AST拿到注释中的diectives
通过directives过滤problems,就是最终需要报出的问题
也就是说eslint的inlineconfig是在lint完AST,拿到各种problems之后生效的,对problems做一次过滤。
那怎么从AST中取出directives的呢?又是怎么过滤problems的呢?
我们分别看一下。
从AST取出directives的源码简化以后是这样的:
functiongetDirectiveComments(ast){constdirectives=[];ast.comments.forEach(comment=>{constmatch=/^[#@](eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(comment.trim());if(match){constdirectiveText=match[1];...directives.push({type:xxx,line:loc.start.line,column:loc.start.column+1,ruleId});}}returndirectives;}其实就是对AST中所有的comments的内容做一下正则的匹配,如果是支持的directive,就把它收集起来,并且记录下对应的行列号。
之后就是对problems的过滤了。
简化后的源码是这样的:
functionapplyDisableDirectives(problems,disableDirectives){constfilteredProblems=[];constdisabledRuleMap=newMap();letnextIndex=0;for(constproblemofproblems){//对每一个probelm,都要找到当前被禁用的rulewhile(nextIndex<disableDirectives.length&&compareLocations(disableDirectives[nextIndex],problem)<=0){constdirective=disableDirectives[nextIndex++];switch(directive.type){case"disable":disabledRuleMap.set(directive.ruleId,directive);break;case"enable":disabledRuleMap.delete(directive.ruleId);break;}}//如果problem对应的rule没有被禁用,则返回if(!disabledRuleMap.has(problem.ruleId)){filteredProblems.push(problem);}}returnfilteredProblems;}functioncompareLocations(itemA,itemB){returnitemA.line-itemB.line||itemA.column-itemB.column;}我们理下思路:
我们要过滤掉problems中被disabled的rule报出的problem,返回过滤后的problems。
可以维护一个disabledRuleMap,表示禁用的rule。
对每一个problem,都根据行列号来从disableDirectives中取出directive的信息,把对应的rule放入disabledRuleMap。
然后看下该problem的rule是否是被禁用了,也就是是否在disabledRuleMap中,如果是,就过滤掉。
这样处理完一遍,返回的problem就是可以报出的了。
这就是eslint的eslint-disable、eslint-enable、eslint-disable-next-line等注释可以配置rule是否生效的原理。
eslint是根据行列号找到对应的comment的,其实很多AST中会记录每个节点关联的comment。
比如babel的AST:
这样可以根据AST来取出注释,之后通过正则来判断是否是directive。
通过行列号来查找comment,通过AST找到关联的comment,这是两种查找注释的方式。
总结注释中的配置在eslint、webpack、terser等工具中都有应用,分别叫inlineconfig、magiccomment、annotation,但都指的同一个东西。
它们都是找到AST中的comments,通过正则匹配下是否是支持的directive(指令),然后取出对应的信息。
找到directive之后,还要找到directive生效的地方,可以用两种方式来查找:一种是根据行列号的比较,一种是根据关联的AST来查找。
找到directive和对应生效的地方之后,就可以根据directive中的信息做各种处理了。
eslint中是在调用完rule之后,拿到所有的problems,通过行列号匹配directive,根据其中的disabledrule过滤掉一些problem。
注释中的配置是一种比较常见的配置方式,适合一些局部的配置。理解了它们的实现原理,能够让我们更好的掌握这种机制。