好奇:eslint的工作原理

需要了解的概念

1. 抽象语法树AST(Abstract Syntax Tree)

维基百科中的解释:
源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

如webpack、rollup、UglifyJS、Lint等很多的工具和库的核心都是通过Abstract Syntax Tree 抽象语法树这个概念来实现对代码的检查、分析等操作的。
AST explorer这个网站可以在线生成AST。

image.png

2. JavaScript Parser

JavaScript Parser,把js源码转化为抽象语法树的解析器。

浏览器会把js源码通过解析器转为抽象语法树,再进一步转化为字节码或直接生成机器码。

一般来说每个js引擎都会有自己的抽象语法树格式,Chrome的v8引擎,firefox的SpiderMonkey引擎等等,MDN提供了详细SpiderMonkey AST format的详细说明,算是业界的标准。

发展到现在可能不同的JavaScript Parser的AST格式会不同,或基于SpiderMonkey AST format,或重新设计自己的AST format,或基于SpiderMonkey AST format优化改进。通过优化抽象语法树,来使程序运行的更快,也是一种提高效率的方法。
常用的JavaScript Parser有:

  • Esprima
  • UglifyJS2
  • Traceur
  • Acorn
  • Shift

eslint用的是espree

抽象语法树的用途

  • 代码语法的检查、代码风格的检查、代码的格式化、代码的高亮、代码错误提示、代码自动补全等等
    • IDE的错误提示、格式化、高亮、自动补全等等
    • Eslint等代码风格工具
  • 代码混淆压缩
    • UglifyJS2等
  • 优化变更代码,改变代码结构使达到想要的结构
    • 代码打包工具webpack、rollup等等
    • CommonJS、AMD、CMD、UMD等代码规范之间的转化
    • CoffeeScript、TypeScript、JSX等转化为原生Javascript

Eslint如何工作的

eslint产生的背景

C 语言诞生之初,程序员编写的代码风格各异,在移植时会出现一些因为不严谨的代码段导致无法被编译器执行的问题。于是在 1979 年,一款叫 lint的程序被开发出来,能够通过扫描源代码检测潜在的错误。

最初javascript开发出来是在web中实现简单的交互(表单提交),后来随着互联网发展,需要展示更多的东西,业务日渐复杂化,前端项目越来越庞大。再加上 JavaScript 本身设计上存在许多缺陷,代码不严谨也可能就会触发神奇的错误。

所以语法检测工具就诞生了。在市场检验下,eslint脱颖而出。参考资料eslint起源

eslint配制

ESlint 被设计为完全可配置的,这意味着你可以关闭每一个规则而只运行基本语法验证,或混合和匹配 ESLint 默认绑定的规则和你的自定义规则,以让 ESLint 更适合你的项目。可以查看详细eslint配置官方文档

执行下面命令可以创建一个配置

eslint --init
/** .eslintrc.js */
module.exports = {
    "extends": "eslint:recommended",
    "rules": {
        // enable additional rules
        "indent": ["error", 4],
        "linebreak-style": ["error", "unix"],
        "quotes": ["error", "double"],
        "semi": ["error", "always"],

        // override default options for rules from base configurations
        "comma-dangle": ["error", "always"],
        "no-cond-assign": ["error", "always"],

        // disable rules from base configurations
        "no-console": "off",
    }
}
  • extends 属性值可以是:

    • 在配置中指定的一个字符串
    • 字符串数组:每个配置继承它前面的配置

ESLint 递归地进行扩展配置,所以一个基础的配置也可以有一个 extends 属性。

  • rules 属性可以做下面的任何事情以扩展(或覆盖)规则:

    • 启用额外的规则
    • 改变继承的规则级别而不改变它的选项:
      • 基础配置:"eqeqeq": ["error", "allow-null"]
      • 派生的配置:"eqeqeq": "warn"
      • 最后生成的配置:"eqeqeq": ["warn", "allow-null"]
    • 覆盖基础配置中的规则的选项
      • 基础配置:"quotes": ["error", "single", "avoid-escape"]
      • 派生的配置:"quotes": ["error", "single"]
      • 最后生成的配置:"quotes": ["error", "single"]

最出名的两个风格:airbnbstandard

rule是怎么工作的?

先了解一下rule的结构,官方文档rule

eslint推荐规则rule列表里我们选一个常见而且简单的规则来学习:no-const-assign 禁止修改 const 声明的变量。在eslint源码库中找到对应的这条规则的源码

const astUtils = require("../util/ast-utils");

module.exports = {
   meta: {
       type: "problem",

       docs: {
           description: "disallow reassigning `const` variables",
           category: "ECMAScript 6",
           recommended: true,
           url: "https://eslint.org/docs/rules/no-const-assign"
       },

       schema: [],

       messages: {
           const: "'{{name}}' is constant."
       }
   },

   create(context) {

       /**
        * Finds and reports references that are non initializer and writable.
        * @param {Variable} variable - A variable to check.
        * @returns {void}
        */
       function checkVariable(variable) {
           astUtils.getModifyingReferences(variable.references).forEach(reference => {
               context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } });
           });
       }

       return {
           VariableDeclaration(node) {
               if (node.kind === "const") {
                   context.getDeclaredVariables(node).forEach(checkVariable);
               }
           }
       };

   }
}

每条rule是一个暴露的nodejs模块,模块包含两个属性:metacreate

  • meta:包含规则的元数据。类别,文档,可接收的参数的 schema,messages等
  • create:create (function) returns an object with methods that ESLint calls to “visit” nodes while traversing the abstract syntax tree (AST as defined by ESTree) of JavaScript code:
    • if a key is a node type or a selector, ESLint calls that visitor function while going down the tree
    • if a key is a node type or a selector plus :exit, ESLint calls that visitor function while going up the tree
    • if a key is an event name, ESLint calls that handler function for code path analysis

我的理解就是create返回的这个对象包含三种不同类型的函数

  • AST中选择器名字命名的函数,例如VariableDeclaration
  • AST中选择器名字+:exit的函数, 列入FunctionExpression:exit
  • code path 抽象的 5 个事件onCodePathStartonCodePathEndonCodePathSegmentStartonCodePathSegmentEndonCodePathSegmentLoop

当触发这个函数发现不符合规则的时候会调用context.report()抛出问题。
比如,当我们代码中出现修改const声明的变量phone的时候,就会抛出phone is constant.的错误

举个有完整的create栗子:array-callback-return 的rule:

function checkLastSegment (node) {
    // report problem for function if last code path segment is reachable
}

module.exports = {
    meta: { ... },
    create: function(context) {
        // declare the state of the rule
        return {
            ReturnStatement: function(node) {
                // at a ReturnStatement node while going down
            },
            // at a function expression node while going up:
            "FunctionExpression:exit": checkLastSegment,
            "ArrowFunctionExpression:exit": checkLastSegment,
            onCodePathStart: function (codePath, node) {
                // at the start of analyzing a code path
            },
            onCodePathEnd: function(codePath, node) {
                // at the end of analyzing a code path
            }
        };
    }
};

code-path-analysis

code-path就是程序的执行路径。我们的代码会被解析成一些code path,单个code path又是多个CodePathSegment组成。

看一下官方文档中的例子,这段if代码段:

if (a && b) {
    foo();
}
bar();
image.png

ESLint 将 code path 抽象为 5 个事件,在rule里可以给这些事件绑定函数。

那eslint是如何触发rule里的函数的呢?

查看官方文档

eslint 对象的主要方法是 verify(),接收两个参数:要验证的源码文本和一个配置对象(通过准备好的配置文件加命令行操作会生成配置)。该方法首先使用 espree(或配置的解析器) 解析获取的文本,检索 AST。AST 用来产生行/列和范围的位置,对报告问题的位置和检索与 AST 节点有关的源文本很有帮助。

一旦AST是可用的,estraverse 被用来从上到下遍历 AST。在每个节点,eslint对象触发与该节点类型同名的一个事件(即 “Identifier”,”WithStatement” 等)。在回退到子树上时,一个带有 AST 类型名称和 “:exit” 后缀的事件被触发,比如 “Identifier:exit” - 这允许规则在正向和逆向遍历开始起作用。每个事件在恰当的 AST 节点可用时触发。

现在我们已经深入的了解rule了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容