乐趣区

十分钟了解eslint配置-编写自定义eslint规则

eslint 介绍

ESLint 是一个开源的 JavaScript 代码检查工具,由 Nicholas C. Zakas 于 2013 年 6 月创建。代码检查是一种静态的分析,常用于寻找有问题的模式或者代码,并且不依赖于具体的编码风格。对大多数编程语言来说都会有代码检查,一般来说编译程序会内置检查工具。

JavaScript 是一个动态的弱类型语言,在开发中比较容易出错。因为没有编译程序,为了寻找 JavaScript 代码错误通常需要在执行过程中不断调试。像 ESLint 这样的可以让程序员在编码的过程中发现问题而不是在执行的过程中。

ESLint 的初衷是为了让程序员可以创建自己的检测规则。ESLint 的所有规则都被设计成可插拔的。为了便于人们使用,ESLint 内置了一些规则,当然,你可以在使用过程中自定义规则。所有的规则默认都是禁用的。

ESLint 使用 Node.js 编写。

eslint 配置

配置方式
  1. 一般都采用 .eslintrc.* 的配置文件进行配置, 如果放在项目的根目录中,则会作用于整个项目。如果在项目的子目录中也包含着.eslintrc 文件,则对于子目录中文件的检查会忽略掉根目录中的配置,而直接采用子目录中的配置,这就能够在不同的目录范围内应用不同的检查规则,显得比较灵活。ESLint 采用逐级向上查找的方式查找 .eslintrc.* 文件,当找到带有 "root": true 配置项的.eslintrc.* 文件时,将会停止向上查找。
  2. 在 package.json 文件里的 eslintConfig 字段进行配置。
具体配置规则

以使用项目为例,简单介绍一下 eslint 的具体配置及作用:

module.exports = {
    parser: 'babel-eslint', // parser 指定解析器,默认的为 espree。babel-eslint 是一个 Babel parser 的包装器,这个包装器使得 Babel parser 可以和 ESLint 协调工作
    parserOptions: {sourceType: 'module', // 设置为 "script" (默认) 或 "module"(ES6)。ecmaFeatures: { // 这是个对象,表示你想使用的额外的语言特性:
            jsx: true // 启用 JSX
        }
    },
    extends: ['eslint:recommended'], // 使用 eslint 推荐的规则作为基础配置,可以在 rules 中覆盖
    plugins: ['html', 'vue', 'prettier', 'import'], // vue 是 eslint-plugin-vue 的简写,此插件的作用是可以让 eslint 识别.vue 中的 script 代码
    rules: {// 0 或者 off 表示规则关闭,出错也被忽略;1 或者 warn 表示如果出错会给出警告(不会导致程序退出);2 或者 error 表示如果出错会报出错误(会导致程序退出,退出码是 1)
        'no-console': 'off',
        'prefer-const': 'error',
        'prettier/prettier': 'warn',
        'prefer-arrow-callback': 'warn',
        'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
    },
    globals: { // 允许在代码中使用全局变量
        location: true,
        setTimeout: true
    }
};

具体的配置文档:http://eslint.cn/docs/user-guide/configuring
具体的 eslint:recommended 支持的规则:https://cn.eslint.org/docs/rules/

“extends”除了可以引入推荐规则,还可以以文件形式引入其它的自定义规则,然后在这些自定义规则的基础上用 rules 去定义个别规则,从而覆盖掉”extends”中引入的规则。

{
    "extends": [
        "./node_modules/coding-standard/eslintDefaults.js",
        // Override eslintDefaults.js
        "./node_modules/coding-standard/.eslintrc-es6",
        // Override .eslintrc-es6
        "./node_modules/coding-standard/.eslintrc-jsx",
    ],
    "rules": {
        // Override any settings from the "parent" configuration
        "eqeqeq": "warn"
    }
}

除了在配置文件中指定规则外,还可以在代码中指定规则,代码文件内以注释配置的规则会覆盖配置文件里的规则,即优先级要更高。平时我们常用的就是 `eslint-disable-next-line
`

忽略检查

可以通过在项目目录下建立.eslintignore 文件,并在其中配置忽略掉对哪些文件的检查。需要注意的是,不管你有没有在.eslintignore 中进行配置,eslint 都会默认忽略掉对 /node_modules/** 的检查。也可以在 package.json 文件的 eslintIgnore 字段进行配置。

eslint 检查原理

要实现静态分析则需要自建一个预编译阶段对代码进行解析。

首先我们看看大部分编译器工作时的三个阶段:

解析:将未经处理的代码解析成更为抽象的表达式,通常为抽象语法树,即 AST。
转换:通过修改解析后的代码表达式,将其转换为符合预期的新格式。
代码生成:将转换后的表达式生成为新的目标代码。

对于 eslint 来说,规则校验发生在将 JavaScript 代码解析为 AST 之后,遍历 AST 的过程中。eslint 采用 Espree 来生成 AST。具体的生成方法在这里。
我们可以使用 AST explorer 来查看代码被解析后生成的 AST。

rules 工作原理

首先来看看 eslint 源码中关于 rules 的编写。eslint 中的 rules 源码存在于 lib/rules 下。每一个 rules 都是一个 node 模块,用 module.exports 导出一个 meta 对象及一个 create 函数。

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

        docs: {
            description: "disallow unnecessary semicolons",
            category: "Possible Errors",
            recommended: true,
            url: "https://eslint.org/docs/rules/no-extra-semi"
        },
        fixable: "code",
        schema: [] // no options},
    create: function(context) {
        return {// callback functions};
    }
};

meta 代表了这条规则的元数据,如这条规则的类别,文档,可接收的参数 schema 等等。

create 返回一个对象,其中定义了一些在 AST 遍历访问到对应节点需要执行的方法等等。函数接受一个 context 对象作为参数,里面包含了例如可以报告错误或者警告的 context.report()、可以获取源代码的 context.getSourceCode()等方法,可以简化规则的编写。

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) {// 在 AST 从上向下遍历到 ReturnStatement node 时执行},
            // 在 AST 从下向上遍历到 function expression node 时执行:
            "FunctionExpression:exit": checkLastSegment,
            "ArrowFunctionExpression:exit": checkLastSegment,
            onCodePathStart: function (codePath, node) {// 在分析代码路径开始时执行},
            onCodePathEnd: function(codePath, node) {// 在分析代码路径结束时执行}
        };
    }
};

遍历 AST 的过程中会以“从上至下”再“从下至上”的顺序经过节点两次,selector 默认会在下行的过程中执行对应的访问函数,如果需要再上行的过程中执行,则需要添加:exit。

详细的原理在官方文档中有说明,点这里。
详细的代码路径分析在这里。

如何编写一个 rules

知道了 rules 的原理,接下来可以自定义一个 rules。每一个 rules 需要有三个以该规则名命名的文件,分别是:

  • 在 lib/rules 目录下:一个源文件(例如,no-extra-semi.js)
  • 在 tests/lib/rules 目录下: 一个测试文件 (例如, no-extra-semi.js)
  • 在 docs/rules 目录: 一个 markdown 文档文件 (例如, no-extra-semi)

接下来我们来编写一个简单的 rules,例如禁止块级注释,当代码中使用了块级注释,eslint 将报错。

rules 文件:

// lib/rules/no-block-comments.js
module.exports = {
  meta: {
    docs: {
      description: '禁止块级注释',
      category: 'Stylistic Issues',
      recommended: true
    }
  },

  create (context) {
    // 获取源代码
    const sourceCode = context.getSourceCode()

    return {Program () {
        // 获取源代码中所有的注释
        const comments = sourceCode.getAllComments()

        const blockComments = comments.filter(({type}) => type === 'Block')

        blockComments.length && context.report({
          node: node,
          message: 'No block comments'
        })
      }
    }
  }
}

rules 的测试文件:

// tests/lib/rules/no-block-comments.js
const RuleTester = require("eslint").RuleTester;
const rule = require("../../../lib/rules/no-block-comments");

const ruleTester = new RuleTester({parserOptions: { ecmaVersion: 2018} }); // You do have to tell eslint what js you're using

ruleTester.run("no-block-comments", rule, {valid: ["var a = 1; console.log(a)"],
    invalid: [
        {code: "var a = 1; /* block comments */ console.log(a)",
            errors: [
                {
                    messageId: "blockComments",
                    line: 1,
                    nodeType: "Block"
                }
            ]
        }
    ]
});

官网的 working with rules 文档中有关于如何编写一个 rules 的详细介绍。

如何使用自定义的 rules

编写好的 rules 需要发布到 npm 上,作为一个 eslint-plugin,在项目中下载下来才能够使用。例子中代码的 npm 在这里。

在项目中的配置:

// .eslintrc.js
module.exports = {
    ...
    "plugins": [
        "eslint-plugin-no-block-comments"
        // 你 publish 的 npm 包名称,可以省略 eslint-plugin
      ],
    "rules": { // 启用的规则及其各自的错误级别
        'no-console': 'off',
        "no-block-comments/no-block-comments": 2 // 引用 no-block-comments 插件中的 no-block-comments 规则
    }
};

之后就可以对代码进行检查了。比如我要检查的代码如下:

// src/index.js
const a = 1;
/*
    这里是块级注释
*/
console.log(a);

在命令行中执行eslint src,就可以看到报错结果。

参考文章:

ESlint 官网

ESLint 工作原理探讨

开发 eslint 规则

退出移动版