【实战】自定义 ESLint Plugin

269次阅读  |  发布于3年以前

背景

之前做过一个小分享——【优化】记一次通过工具减少 Git 冲突[1]。主要讲的是通过利用 git hooks 在代码提交之前给相关的代码排序,从而减少合代码时候的冲突。

上次同事提醒说,这个 Eslint 就可以做到。我回去查了一下,还真可以,详情见 sort-keys[2]。假如使用了这条规则,就是要求对象写法要遵循一定的顺序。比如开启这个规则的话,默认情况下下面的代码就会报错:

let obj = {a: 1, c: 3, b: 2};

应该为:

let obj = {a: 1, b: 2, c: 3};

但是其实我们的诉求中,还有一种场景,那就是对象数组。比如下面这个场景,我需要根据 label 去决定对象在数组中顺序(注意:我们这个场景下数组的顺序对业务是没有影响的),Eslint 这个规则就无能为力了。

const FlowList = [
  { value: '5', label: 'a' },
  { value: '2', label: 'C' },
  { value: '1', label: 'B' }
];

另外,我们知道 ESLint 规则可以针对某个文件夹或者某个文件生效,那能不能只针对于某个代码块呢?

那我们如何通过 Eslint 暴露给我们的能力去实现这些点呢?

ESLint 是什么?

官方如下:

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误

ESLint 具有以下特点:

ESlint 我们离不开 AST(抽象语法树),我们可以通过 astexplorer[4] 直观看到 Espree 处理后生成的 AST 的结构。比如 var a = 1; 如下所示:

image-20210812220540504

竟然我们知道它的结构,我们就可以直接去检测它合不合法了。

我们来讲一个重要的概念——AST Selectors :它是一个字符串,可用于匹配抽象语法树(AST)中的节点。这对于在代码中描述特定的语法模式非常有用。AST 选择器的语法与 CSS 选择器的语法类似。如果你以前使用过 CSS 选择器,那么 AST 选择器的语法应该很容易理解。这个在我们后面自定义规则的时候非常重要。它的语法可以看官方文档[5]。

ESlint 的原理

在开始书写我们的规则,我们看看 ESlint 具体的实现是怎么做的(这里只说明单条的 Rule 是怎么书写的,整体的 ESlint 作用流程这里不展开)。就以之前提到的 sort-keys[6] 为例。

每个规则都会有三个重要的文件:

lib/rules/sort-keys.js 中我们可以找到上面规则相应的源码。规则的源文件导出具有以下属性的对象。类似如下:

module.exports = {
  // 包含规则的元数据
  meta: {
    // 规则类型
    type: "suggestion",
    // 文档
    docs: {
      description: "require object keys to be sorted",
      category: "Stylistic Issues",
      recommended: false,
      url: "https://eslint.org/docs/rules/sort-keys",
    },
    schema: [
      // 可以传的一些参数
      {
        enum: ["asc", "desc"],
      }
    ],
    // 提示信息
    messages: {
      sortKeys:
        "Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
    },
  },
  create(context) {
    return {};
  },
};

create 返回的是一个对象,其中 key 就是上面提到的 AST Selector,在 AST Selector 中,我们可以获取对应选中的内容,随后我们可以针对选中的内容作一定的判断,看是否满足我们的规则,如果不满足,可用 context.report()抛出问题,ESLint 会利用我们的配置对抛出的内容做不同的展示。

AST Selector的末尾添加 :exit 将导致在遍历过程中退出匹配节点时调用侦听器,而不是在输入匹配节点时。

自定义 ESlint 插件

基于 Yeoman generator[8] (一个快速帮你搭建工程的脚手架工具),可以快速创建 ESLint plugin 项目。

npm i -g yo
npm i -g generator-eslint
// 创建一个plugin
yo eslint:plugin
// 创建一个规则
yo eslint:rule

我创建的目录结构如下:

├── README.md
├── docs # 文档
│   └── rules
│       ├── array-sort-object.md
│       └── sort.md
├── lib # 源代码,规则文件
│   ├── index.js
│   └── rules
│       ├── array-sort-object.js
│       └── sort.js
├── package.json
├── tests # 单元测试文件
│   └── lib
│       └── rules
│           ├── array-sort-object.js
│           └── sort.js
└── yarn.lock

如何做到只检测部分代码?

我们知道 ESlint 的检测可以指定到文件维度,但是我们希望只针对部分的代码进行检测。要不然像对象数组顺序,假如都开了检测,将会有很多报错或者警告。

方法是有的,我们发现,ESlint 是可以通过 getCommentsInside 方法获取到某个 AST Selector 中的注释,返回给定节点内所有注释标记的数组。比如以下:

const FlowList = [
  // eslint sortBy:'label'
  { value: '5', label: 'a' },
  { value: '2', label: 'C' },
  { value: '1', label: 'B' }
];
create: function (context) {
    // 获取到顺序的配置,默认是升序
    const order = context.options[0] || "asc";
    // variables should be defined here
  return {
    ArrayExpression: (node) => {
      console.log('getCommentsBefore:', context.getCommentsInside(node))
    }
  }

打印出来的结果如下,我们就可以利用这个信息进行处理。只有评论命中某个规则的时候,才去处理这段代码

image-20210812231108912

实现对象数组排序

整体的实现代码如下,实现上并不难。整体思路:

总结

Eslint 对于一个团队的代码规范是非常重要的,Eslint 自身带有很多有用的规则,本文介绍了 ESlint 的基础原理以及如何自定义 Eslint 插件来解决对象数组排序的问题,除此之外,我们可能还有其他的场景可以进行尝试,欢迎大家参与讨论~

参考

参考资料

[1]【优化】记一次通过工具减少 Git 冲突: https://juejin.cn/post/6895534290411454477

[2]sort-keys: https://eslint.org/docs/rules/sort-keys#rule-details

[3]Espree: https://github.com/eslint/espree

[4]astexplorer: https://astexplorer.net/

[5]官方文档: https://eslint.org/docs/developer-guide/selectors

[6]sort-keys: https://eslint.org/docs/rules/sort-keys#rule-details

[7]官方文档: https://link.juejin.cn?target=http%3A%2F%2Flink.zhihu.com%2F%3Ftarget%3Dhttps%3A%2F%2Feslint.org%2Fdocs%2Fdeveloper-guide%2Fworking-with-rules%23rule-basics

[8]Yeoman generator: https://yeoman.io/authoring/

[9]ESLint 工作原理探讨: https://juejin.cn/post/6844903749886935053#heading-5

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8