自定义 ESlint 规则

4,720 阅读4分钟

前言

实际业务中,我们可以把团队的编码规范和最佳实践通过自定义规则落地到实际项目中,在编码阶段对开发者进行提示和约束,这对于多人协助、代码维护、统一代码风格都会有很大的帮助。

认识 ESlint

ESLint 是一个开源的 JavaScript 代码检查工具,通过静态的分析,寻找有问题的模式(分号、空格等)或者代码。默认使用 Espree 解析器将代码解析为 AST (抽象语法树),然后再对代码进行检查。

看下最简单的一段代码使用 espree 解析器转换成的抽象语法树结构,此处可以使用 astexplorer (astexplorer.net/) 快速查看解析成的 AST 结构:

示例片段:

let age = 10

转换出的结果:

{
  "type": "Program",
  "start": 0,
  "end": 12,
  "range": [
    0,
    12
  ],
  "body": [
    {
      "type": "VariableDeclaration", // 变量声明
      "start": 0,
      "end": 12,
      "range": [
        0,
        12
      ],
      "declarations": [
        {
          "type": "VariableDeclarator", // 变量声明符
          "start": 4,
          "end": 12,
          "range": [
            4,
            12
          ],
          "id": {
            "type": "Identifier", // 标识符
            "start": 4,
            "end": 7,
            "range": [
              4,
              7
            ],
            "name": "age"
          },
          "init": {
            "type": "Literal",  // 直接量
            "start": 10,
            "end": 12,
            "range": [
              10,
              12
            ],
            "value": 10,
            "raw": "10"
          }
        }
      ],
      "kind": "let"  // 表示使用的是 let 关键字
    }
  ],
  "sourceType": "module"
}

代码转换为 AST 后,可以很方便的对代码的每个节点进行检查。

ESlint 插件

自定义 ESLint 规则需要开发 ESLint 的插件来实现,将规则写到 ESLint 插件中,然后在业务代码中添加 ESLint 配置,并引入 ESLint 插件中的规则。

演示项目源码:github.com/GYunZhi/esl…

初始化项目

ESLint 官方为了方便开发者开发插件,提供了使用 Yeoman 模板 generator-eslint。

1、安装脚手架工具:

npm install -g yo generator-eslint

2、创建一个文件夹:

mkdir eslint-plugin-demo
cd eslint-plugin-demo

3、初始化 ESlint 插件项目:

yo eslint:plugin

# 命令行交互流程,流程结束后生成 ESLint 插件项目基本框架
? What is your name? gongyz // 作者名字
? What is the plugin ID? fun   // 插件 ID
? Type a short description of this plugin: xxx // 插件描述
? Does this plugin contain custom ESLint rules? Yes // 插件是否包含自定义ESLint规则
? Does this plugin contain one or more processors? No // 插件是否包含一个或多个处理器

创建规则

上一个命令行生成了 ESLint插 件的项目模板,接下来我们创建一个具体的规则。

yo eslint:rule // 生成 eslint rule的模板文件

示例规则:检查代码中是否存在 http 链接,并提示应该使用 https 链接

? What is your name? gongyz
? Where will this rule be published? (Use arrow keys) // 规则将在哪里发布
❯ ESLint Plugin  // ESLint 插件
	ESLint Core  // 官方核心规则 (目前有200多个规则)
? What is the rule ID? no-http-url  // 规则 ID
? Type a short description of this rule: 检查代码中是否存在 http 链接  // 规则描述
? Type a short example of the code that will fail:   // 输入一个失败例子的代码(跳过就行)

添加了规则之后的项目结构:

.
├── README.md
├── docs 
│   └── rules
│       └── no-http-url.md // 规则文档
├── lib
│   ├── index.js 入口文件
│   └── rules
│       └── no-http-url.js // 具体规则源码
├── package.json
└── tests
    └── lib
        └── rules
            └── no-http-url.js // 测试文件

编写规则

编写一个规则前先通过 astexplorer (astexplorer.net/) 观察代码解析成的 AST,找到要解析的代码。然后进行一些校验。

如下面的示例代码:

let url = 'http://www.linklogis.com'

img

根据分析,我们写好了下面的规则:

/**
 * 检查代码中是否存在 http 链接
 */
 module.exports = {
  meta: {
    type: 'suggestion', // 规则类型
    docs: {}, // 文档
    fixable: 'code', // 是否自动修复 null | code
    messages: {
      noHttpUrl: 'Recommended "{{ url }}" switch to HTTPS' // 带占位符的提示信息
    }
  },
  create (context) {
    return {
      // Literal 是选择器(类似 CSS 选择器)
      Literal: function handleRequires(node) {
        if (
          node.value &&
          typeof node.value === 'string' &&
          node.value.indexOf('http:') === 0
        ) {
          context.report({
            node,
            messageId: 'noHttpUrl',
            data: {  // (可选的) data 中的数据可用作 message 的占位符
              url: node.value
            },
            // 替换 http 为 https
            fix: fixer => {
              return fixer.replaceText(
                node,
                `'${node.value.replace('http:', 'https:')}'`
              )
            }
          })
        }
      }
    }
  }
}
  • meta 对象:规则的元数据,如类别,文档,可接收的参数(通过 schema 字段配置) 等。

  • create 方法: 返回一个对象,对象的属性设为选择器(也可以是一个事件名称),ESLint 会收集这些选择器,在 AST 遍历过程中会执行所有监听该选择器的回调。

  • context.report:用来抛出警告或错误,并能提供自动修复功能(需要添加 fix 配置)

测试用例

为了保证代码的完整性和安全性,我们给上面的规则加上测试用例:

const rule = require('../../../lib/rules//no-http-url')
const { RuleTester } = require('eslint')

const ruleTester = new RuleTester()

ruleTester.run('no-http-url', rule, {
  valid: [
    {
      code: "var test = 'https://test.com';"
    }
  ],

  invalid: [
    {
      code: "var test = 'http://test.com';",
      output: "var test = 'https://test.com';",
      errors: [
        {
          message: 'Recommended "http://test.com" switch to HTTPS'
        }
      ]
    },
    {
      code: "<img src='http://test.com' />",
      output: "<img src='https://test.com' />",
      parserOptions: {
        ecmaFeatures: {
          jsx: true
        }
      },
      errors: [
        {
          message: 'Recommended "http://test.com" switch to HTTPS'
        }
      ]
    }
  ]
})

根目录执行:

npm run test

img

插件安装

开发完插件后可以发布到 npm,然后业务代码中安装你的 ESLint 插件:

npm install eslint-plugin-fun -D // eslint-plugin-fun 是 npm 包名

如果你的 npm 包还未发布,可以使用 npm link 进行本地调试。

项目配置

业务项目中 ESlint 配置如下:

// .eslintrc.js 

module.exports =  {
  // 引入插件
  plugins: ['fun'], // eslint-plugin- 可以省略,只填 fun

  // 配置规则
  rules: {
    'fun/no-http-url': 1
  }
}

参考资源