范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

ESLint机制分析与简单插件实践

  作者:Ye Peng前言
  代码是写给人看的,所以一份好的代码,是要让水平不一的阅读者,都能够理解代码的本意。每个人的代码风格是不可能完全相同的,例如在一个文件里,有的以两个空格做缩进,有的以四个空格做缩进,有的使用下划线,有的使用驼峰,那么它的阅读体验就会变得很差。
  所以如何来对代码进行约束,使团队的代码风格尽量统一,不产生更多的理解成本,是一个需要解决的问题。众所周知, 懒是社会生产力进步的源动力,所以...
  在前端工程化的标准中有一项就是自动化,自动化当中就包括了代码规范自动化。实现代码规范自动化可以解放团队生产力,提升团队生产效率...所以 ESLint、TSLint、StyleLint 这些工程化插件应运而生。
  而最近在笔者团队也在统一不同的项目之间的规范差异,相信大家也都遇到了大段飘红的现象,今天咱来简单探究一下背后涉及到的原理。 What is ESLint/Lint?
  首先,提到 ESLint,应该会想到两种东西,一个是 ESLint 的 npm 包,也就是我们 devDep 里面的, 另一个是我们所安装的比如 VSCode 的 ESLint 插件,那么这两个东西有什么联系呢。
  npm 包:是实际的 Lint 规则以及我们执行 Lint 的时候,控制代码如何去进行格式化的。
  VSCode 插件: 实际指向我们项目的 /node_modules/eslint 或者全局的 eslint,通过 ESLint 的规则,告诉 IDE,哪些地方需要飘红。也就是说插件是在解析我们的打开的文件,同时和规则对比,是否存在 ESLint 问题。 以及可以通过我们的 IDE 配置,在不同的时机去执行我们的 Lint,比如保存自动格式化。
  总而言之,ESLint 规则就是对我们的代码风格和代码中潜在的一些错误和不规范用法的一个约束,通过 npm 包的形式引入项目,同时通过 IDE 的插件,读取 npm 包规则,对我们的代码进行错误提示。 How to Use it?
  如何在项目配置 ESLint 就不在本文赘述了。 大多数脚手架其实都会给你初始化好基本的 ESLint。 涉及到的工具不同可能会有些许的不一样,不过都大差不差。这段讲一下 ESLint 中的主要配置项。 如果有兴趣深一步研究,可以移步  ESLint 的官网文档 [1] ,对 ESLint 默认的规则集[2] 感兴趣也可移步官网文档一同查看。
  打开一个 eslintrc 文件,一般来说,有几个选项。这里以 json 为例,来简单说明下每个字段。 {     "extends": "", // 规则集继承自某个规则集     "root": "true", // 找到这后,不再向上级目录寻找     // 解析选项     "parserOptions": {         "ecmaVersion": 6, //  指定你想要使用的 ECMAScript 版本 3/5/6/7/8/9         "sourceType": "module", // "script"(default) or "module",标明你的代码是模块还是script         "ecmaFeatures": { // 是否支持某些feature,默认均为false             "globalReturn": true, //是否允许全局return             "impliedStrict": true, //是否为全局严格模式             "jsx": true         }     },     //自定义解析器,官方支持下列四种,也可以自己定义解析器。     "parse": "espree" | "esprima" | "Babel-ESLint" | "@typescript-eslint/parser",     "plugins": ["a-plugin"], //第三方 插件a     "processor": "a-plugin/a-processor", // 制定处理器为插件a的处理器     "rules": {         "eqeqeq": "error"     }     // 指定一些全局变量,类似于global.d.ts的作用     "globals": {         "var1": "writable",         "var2": "readonly"     }     // 忽略哪些文件     "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] }
  ESLint 支持以下几种格式的配置文件,如果同一个目录下有多个配置文件,ESLint 只会使用一个。优先级顺序如下: .eslintrc.js  .eslintrc.yaml  .eslintrc.yml  .eslintrc.json  .eslintrc  package.json
  同时 ESLint 也支持对每个目录配置不一样的规则,对于 mono 仓库下,可能每个 repo 的 ESLint 都有些许的区别,这个时候我们就可以采用下面的目录格式,根目录下存在基本规则,子 app 下存在特定的规则。子 rc 是对父 rc 的一个 override,但是如果我们在 app/.eslintrc.js 中设置了 root:true,那么对于 test.js,父目录中 rc 使用的规则,在 app 中不会生效。 packages   package.json  .eslintrc.js   lib      test.js   app     .eslintrc.js     test.js Why does it work?
  AST
  他是为什么能够生效的。 这里就要提到我们前端方方面面都要涉及到的 AST 了,感谢新时代。
  ESLint 是基于抽象语法树来进行工作的,ESLint 默认使用的编译器(parse)是  Espree[3] ,通过它来解析我们的 JS 代码生成 AST,基于 AST,我们就可以对我们的代码进行检查和修改了。
  通常我们的 Babel 编译分为下图这几步,编译/转换/生成。ESlint 和它对比,只有第一步是一致的, 因为我们只需要拿到 AST 中的部分信息,同时直接在源码中进行提示和操作就行,并不需要 transform 和后续的生成代码。
  解析
  现在我们通过 demo 来探究他背后的原理以及转换的方式。 首先,我们需要加载和解析我们的源代码。 这就是编译器将我们的代码转换成 AST 树的一个过程。因为已经全面拥抱 typescript(主要是因为 espree 没有类型注解,我难受),所以本文使用  @typescript-eslint/parser  来作为我们的编译器。 这里有个小坑,如果在 VSCode 安装了 import cost 插件的话,他去解析这个 parser 会特别卡,所以可以暂时禁用。const foo =   "anthony" const bar = "dst" import fs from "fs"; import path from "path"; import * as tsParser from  "@typescript-eslint/parser";   const filePath = path.resolve("./src/test.ts") const text = fs.readFileSync(filePath, "utf8") // 编译成 AST 这里是不是和eslint的配置项对上了,没错就是透传而已 const ast = tsParser.parse(text,{     comment: true, // 创建包含所有注释的顶级注释数组     ecmaVersion: 6, // JS 版本     // // 指定其他语言功能,     // ecmaFeatures: {     //     jsx: true, // 启用JSX解析     //     globalReturn: true // 在全局范围内启用return(当sourceType为"commonjs"时自动设置为true)     // },     loc: true, // 将行/列位置信息附加到每个节点     range: true, // 将范围信息附加到每个节点     tokens: true // 创建包含所有标记的顶级标记数组 })
  然后我们将获得的 AST 打印一下,简单从下图可以看到主要包含的内容。 本地打印出来可能不太方便阅读,也可以使用 在线的工具[4] ,将解析器设置为@typescript-eslint/parser  。相对于 espree 来说,ts 解析多出来的部分中,比较关键的就是图中这段,决定我们如何去解析他的类型。
  AST 就是记录了读取源文件之后的文本内容的各个单位的位置信息,这样我们就可以通过操作 AST 修改需要修改的内容,然后再根据修改后的 AST 信息进行修改对应的文本内容。比如我们把上文中的  const   关键字修改成 let   ,那么我们就先对 AST 对应的const  内容进行修改为 let   ,得到修改之后的 AST 数据,再根据修改后的 AST 数据去修改对应的文本内容。所谓的修改就是字符串替换,因为我们已经知道了对应的位置信息。
  SourceCode
  但是根据上面我们可以看到,直接根据 AST 去查找然后比对替换,效率是很低的,而且嵌套比较深。这个时候 ESlint 是怎么干的呢?他生成了一个新的结构用于我们操作,也就是 SourceCode  。有兴趣进一步探究可以自行查阅源码的 sourcecode/source_code.js  部分。简单来说,就是构建了一个 SourceCode 实例,接受两个参数,原文 text 和解析后的 AST,然后返回我们一个包含茫茫多方法的实例对象。
  我们在 demo 项目中装一个 eslint 然后引入 SourceCode,看看构造后的对象是个什么玩意。 import { SourceCode } from "eslint"; // ....  // const sourceCode = new SourceCode(text,ast); // 这打个断点,看看sourceCode结构
  简单来讲解(摘抄)一下实例对象里面的一些属性和 __proto__  上的方法,完整属性可以查阅官网/源码/类型注解。hasBOM:是否含有  unicode bom[5] ;lines:将我们的每一行切割,分行形成的一个 array; linsStartIndices:每行的开始位置; tokenAndComments:token 和 comment 的一个有序集合; getText(node?: ESTree.Node, beforeCount?: number, afterCount?: number): string;  isSpaceBetweenTokens(first: AST.Token, second: AST.Token): boolean;   两个 token 间是否有空格;visitorKeys:存在的 key 值。
  OK,前置的一些知识我们已经介绍的差不多了。 接下来结合实际的 rules demo 来进行讲解。
  规则模版
  相信如果有写过 VSCode 插件的同学应该对 Yeoman 不陌生,ESLint 也有提供基于 Yeoman 的一套脚手架用于生成模版。
  首先全局安装 eslint 的脚手架, npm install -g yo generator-eslint  ,然后通过下面的一些交互式命令行操作来初始化我们的操作。
  通过初始化,我们可以看到一个以下的文件的壳子,我们在里面添加一些我们上面所讲到的东西。打开我们生成的规则模版文件,同时在里面添加一些规则和提示(注意,这里我的写法不规范,我将两种无关规则放在了一个规则文件里)。 "use strict"; /** @type {import("eslint").Rule.RuleModule} */ module.exports = {   meta: {     type: "problem", // `problem`, `suggestion`, or `layout`     docs: {       description: "xxxx",       recommended: false,       url: null, // URL to the documentation page for this rule     },     messages:{       temp:"不样你用字面量作为函数的参数传入",       novar: "不样你用var声明",       noExport: "退出时执行这个"     },     fixable: "code", // Or `code` or `whitespace`     schema: [], // Add a schema if the rule has options   },    create(context) {     // variables should be defined here     const sourceCode = context.getSourceCode();     return {       ArrowFunctionExpression:(node)=>{         if(node.callee.name !== "abcd") return;         if(!node.arguments) return;         node.arguments.forEach((argNode,index)=>{           argNode.type === "Literal" && context.report({             node,             messageId: "temp",             fix(fixer){               const val = argNode.value;               const statementString = `const val${index} = ${val} `;               return [                 fixer.replaceTextRange(node.arguments[index].range, `val${index}`),                 fixer.insertTextBeforeRange(node.range, statementString)               ]             }           })         })       },       "Program:exit"(node) {             context.report({                 node,                 messageId: "noExport",             });       },       VariableDeclaration(node){         if(node.kind === "var") {           context.report({               node,               messageId: "novar",               fix(fixer) {                   const varToken = sourceCode.getFirstToken(node)                   return fixer.replaceText(varToken, "let")               }           })       }     }     };   }, };
  关键函数
  在这个 demo 里面,我们看到几个东西,一个是 create 函数的参数  context   以及他的返回值,还有就是context  上提供的report  方法 以及 report 接受的fix  参数。 这几个加起来,形成了我们一条规则的校验逻辑,通过遍历,我们到了某个 AST 节点,如果某个 AST 节点满足了我们所写的某条规则,我们进行 report,同时提供一个修复函数,修复函数通过 token 或者 range 来决定对某处进行文本替换。
  接下来,挨个来讲解这些东西,首先是 context 的上下文形成,这个没有什么好说的,其实就是创建了一个对象,然后提供了一些一些方法,供我们在插件中访问上下文使用,然后对于每个 rule 都在 createRuleListener 中都创建了一个 listener,这里我们在后面串整体流程时还会再过一遍。
  接着是 report 方法,简单分析下这块代码,其实就是通过一系列的操作,然后往 lintingProblem 这个数组里面推了一个 problem。 这个 problem 包含一些错误信息,AST 信息等等。
  最后是我们的 fix,我们上面用到的所有 replace 方法,其实都殊途同归,最后回到了这里,大道至简,简单的 slice 和 += 完成了我们的修复动作。
  基本上一个插件涉及到的核心几个东西,都简单解释了下。 现在我们来串一串整体检测和修复的流程,也就是源码中 linter.js  中的runRules  方法。
  整体流程
  我们在跑规则的时候,肯定需要的是对 AST 进行遍历,同时做一些操作。首先做了一个什么操作呢,调用了一个实例方法 Traverser.traverse  ,传入了ast  和一个对象,包含enter  、leave  和visitorKeys  。这个函数的作用就是进行一个递归遍历,同时在遍历的时候通过 enter 和 leave 我们在队列中存储了两个相同的节点,一个是进入时,一个是退出时,方便我们后续处理。这里涉及到一个设计模式,访问者模式(用于数据和操作解耦),通过在遍历时加上 isEntering,可以让我们决定是在进入时还是退出时执行访问者逻辑。
  接着我们需要把我们的所有规则都给像上面讲的给创建成 ruleListener,然后在我们的 nodeQueue 后续遍历时,触发某些逻辑。 当然,这里大家可能都想到了订阅发布模式,这个也是在我们整个逻辑中比较重要的一环,遍历时,通过 emit 推送消息,然后让 ruleListener 决定是否需要执行某些逻辑,所以,我们需要对 Listener 订阅上某些事件。
  接下来,就是对我们的 nodeQueue 遍历了,通过我们节点上打上的标,来决定是在执行进入逻辑还是离开逻辑。这里我就不展开讲具体的细节了,其实简单理解就是通过 enter 和 leave 的时候去触发不同的 visitor 的动作。
  后语
  本文仅是初步的探索了背后的原理,根据原理,后续可以做的一些例如 ESLint 插件等等并没有详细的阐述,大家可以自行探索。
  最后送大家一句话,linus 说的,也是我比较信奉的一句话:talk is cheap, show me the code,想了解一个东西,最好的办法就是简单实现它。我相信大家在解析完它的流程后,都能够简单实现一个 ESLint 的小 demo,以及能够上手写一写 eslint-plugin。 参考资料
  [1] ESLint 的官网文档 : https://eslint.org/docs/latest/user-guide/configuring/configuration-files
  [2] ESLint 默认的规则集: https://eslint.org/docs/latest/rules/
  [3] Espree: https://github.com/eslint/espree
  [4] 在线的工具: https://astexplorer.net/
  [5] unicode bom: https://en.wikipedia.org/wiki/Byte_order_mark

恐龙灭绝和现代人有何相似之处?地球上的下一个恐龙会是人类吗?恐龙的灭绝和现代人之间有什么相似之处?人类会成为地球上的下一只恐龙吗人类世界的科学技术发展如此之快,以至于连人类都认为自己是自己命运的主宰。但事实果真如此吗?人类能用今天的科学技术梦幻西游手游联动动画葫芦兄弟推出首款子女时装着新衣,游三界!梦幻西游手游携手上海美术电影制片厂,为亿万玩家带来了葫芦兄弟联动款子女时装。作为全新子女时装系统月华童衣的首款产品,葫芦兄弟联动款时装将为玩家还原动画片葫芦兄弟的美世界上50个最美丽的地方(排名不分先后)世界上50个最美丽的地方(排名不分先后)第1位美国大峡谷第2位澳大利亚的大堡礁第3位美国佛罗里达州第4位新西兰的南岛第5位秘鲁印加遗址好望角第6位印度金庙第7位拉斯维加斯第8位悉尼错过等一年!金山这里的枫景独好随着气温逐渐走低,不少地方的枫叶都进入了变色期。在花开海上生态园,20多种秋色叶树进入了最佳观赏期。五彩斑斓,多姿绚丽的枫林渲染了秋光湖滨,给秋季增添了无限的美好遐想空间。在花开海家人被癌症盯上,饮食上需牢记四个要点对于患有癌症的人来说,营养支持是很重要的,因为营养及时提高,能够让身体抵抗力提高,同时有足够的能量维持正常的生命活动。如果在饮食过程中,方法不合理,营养供应不到位,可能会缺乏重要营滚蛋吧!口腔溃疡君近日,小李起床后感到嘴里一阵刺激难忍的灼痛感,心想一定是又长溃疡了,许是最近熬夜加班,聚会应酬,饮食比较油腻辛辣,上火了导致的口腔溃疡。口腔溃疡也被称为口疮,是指出现在口腔内唇上腭舌边有齿痕多半是脾虚湿气重,中医教你健脾利湿祛湿今天我们聊一聊有关齿痕的话题,我们正常人的舌头是很光滑的,而且舌边也是很顺溜的一种感觉,有的人的舌苔就不一样,他有齿痕,就是舌头的两边有一个一个的牙印,这就是典型的齿痕舌。为什么会脾胃不好怎么调?粳米米的文化博大精深,圆米珍珠米东北大米都属于粳米,粘稠性较强,适合煮粥。我们平时用来煮饭的米更加修长的,称之为籼米。粳米和籼米都可煮饭,差别不大,但粳米煮粥更加粘稠,籼米香硬耐饥杨超越戴破洞毛线帽,穿泰迪大衣配牛仔裤,化身保暖小绵羊点击上方蓝字关注我人气小花杨超越出生在农村底层的锦鲤女孩,一直以来都特别的朴素和真实,一举一动都非常接地气,前一段刚刚回农村吃酒席的她,总是带给人一种很真实的明星生活状态。这不,寒多亏了盗墓贼的侵扰,汉文帝的陵墓才被发现,推翻了以前的说法西安东郊白鹿原有个叫凤凰嘴的地方,这个凤凰嘴在很长的时间内被认为是汉文帝的陵墓所在地。不过,根据现在的考古确定,凤凰嘴没有帝王陵墓,真正的汉文帝陵墓却是在西安江村,多亏了盗墓贼的侵河北这座博物馆免费开放,故宫前院长单霁翔任名誉馆长,值得一看作为千年古城的正定,绝对配得上自己拥有一座博物馆。2020年,这座博物馆才姗姗来迟,正式向公众免费开放。正定博物馆位于隆兴寺东侧,建筑面积1。6万平方米,地上一层,地下两层,设有9
一人一车自驾旅行475天,每天住酒店,这些东西带着比较有用上一篇文章里我介绍了我车上配备的一些车载装备,这一次我来介绍一些旅行吃住方面的准备,因为要长期在外面,整天吃饭店有时实在是没有胃口,有时候会考虑自己做点吃的,所以我的车上带了差不多辽宁又发现神秘龙潭,被游客称为东北小九寨,人间仙境天然氧吧说起岫岩,很多朋友不太了解这个县,对它也很陌生,基本都是停留在干旱和与贫穷这个印象。其实,这是对岫岩的一个误解,当我踏进这块宝地,结果让我大吃一惊。岫岩县历史遗存丰富,城市空间格局周三这三个板块或将迎来大幅上涨一冬奥会板块。冬奥会已经日益临近,冬奥会概念行情也进入了最后阶段。前一阶段,这个板块爆发了力度较强的一波行情,板块动不动掀起涨停潮,不少个股收获了不少涨幅,也是这段时间大盘调整以来高通和联发科慌了吗?来源内容由半导体行业观察(IDicbank)编译自digitaltrend,谢谢。十多年来,高通和联发科一直是最佳智能手机制造商的默认选择。您看到的几乎所有智能手机或平板电脑都由高iPhone13能透视?网友千万别拍人大家好,欢迎来到黑马公社不知道还有多少小伙伴记得,一加8Pro上的那颗滤光镜头。一加本以为依靠它实现的秋意滤镜是创新是好玩,但是偏偏就因为它有一定的穿透能力,让一加8Pro陷入了透2021年8款手游全球收入超10亿美元PUBGMobile第一IT之家12月21日消息,SensorTower数据显示,目前,包括PUBGMobile王者荣耀原神在内的8款移动游戏在2021年从AppStore和谷歌Play获得超过10亿美元S26赛季增设两个新段位,但是上分却会变轻松,这是因为什么?对于王者荣耀的玩家来说,上分是每个赛季都要做的事情。但是,由于状态心态游戏匹配机制游戏环境游戏理解个人操作互动分歧思路分歧等原因,造成了输得莫名其妙,赢得坎坎坷坷的现状。也因为这种颜值爆棚!11个适合寒冬去的海岛,风景绝美又小众!赶紧收藏面朝大海,春暖花开,寒冬已至,在大海边总能感受到温暖,今天就为大家推荐一些国内的小众温暖岛屿,大家快快整理行装,到这些温暖又宜居的地方避避寒吧!这11个小众岛屿,不用看人从众,也不泰国旅游沙盒除普吉外新增甲米攀牙苏梅岛帕岸岛和涛岛泰国随着TestGo免隔离计划无限期暂停,更多地区获准在沙盒计划下迎接旅客,旅游沙盒允许完全接种疫苗的人入境,并停留在指定区域或省份,而无需在酒店或隔离点隔离。从1月11日开始,除泰国是一个神秘的地方,也是很多男游客乐于向往的地方泰国虽然是东南亚地区的国家之一,但是这几年,凭借旅游行业慢慢地受到了众人的好评。毕竟众多的中国游客,他们想去到不同的地方增加见识,而且还要了解到当地的风土人情。(此处已添加小程序,1米63泰国梅西加盟日本联赛霸主!转会费5亿日元,曾两破国足北京时间1月11日,J联赛冠军川崎前锋官方宣布签下泰国国脚颂克拉辛。根据此前多家日本媒体的报道,颂克拉辛的转会费高达5亿日元(约人民币2759万元人民币)。下赛季,颂克拉辛将跟随川