好奇:eslint的工作原理

2019-03-14  本文已影响0人  RiverSouthMan

需要了解的概念

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有:

eslint用的是espree

抽象语法树的用途

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",
    }
}

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

最出名的两个风格: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

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

当触发这个函数发现不符合规则的时候会调用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了。

上一篇 下一篇

猜你喜欢

热点阅读