前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【实战】自定义 ESLint Plugin

【实战】自定义 ESLint Plugin

作者头像
GopalFeng
发布2022-08-01 19:50:23
7840
发布2022-08-01 19:50:23
举报
文章被收录于专栏:前端杂货铺-Gopal

背景

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

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

代码语言:javascript
复制
let obj = {a: 1, c: 3, b: 2};

应该为:

代码语言:javascript
复制
let obj = {a: 1, b: 2, c: 3};

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

代码语言:javascript
复制
const FlowList = [
  { value: '5', label: 'a' },
  { value: '2', label: 'C' },
  { value: '1', label: 'B' }
];

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

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

ESLint 是什么?

官方如下:

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

ESLint 具有以下特点:

  • 使用 Espree[3] 解析 JavaScript。
  • 使用 AST 去分析代码中的模式。
  • 完全插件化的。每一个规则都是一个插件,提供了足够的可拓展能力,让我们更好的定义使用规则。

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 目录中的是源文件,具体的校验逻辑可以在这里写。
  • tests/lib/rules 目录中是测试文件,写具体的测试用例。
  • docs/rules 文档目录。

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

代码语言:javascript
复制
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 {};
  },
};

  • meta :代表了这条规则的元数据,如其类别,文档,可接收的参数的 schema 等等,官方文档[7]对其有详细的描述,这里不做赘述。
  • create: meta 表达了我们想做什么,那么 create 则用表达了这条 rule 具体会怎么分析代码。

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

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

自定义 ESlint 插件

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

代码语言:javascript
复制
npm i -g yo
npm i -g generator-eslint
// 创建一个plugin
yo eslint:plugin
// 创建一个规则
yo eslint:rule

我创建的目录结构如下:

代码语言:javascript
复制
├── 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 中的注释,返回给定节点内所有注释标记的数组。比如以下:

代码语言:javascript
复制
const FlowList = [
  // eslint sortBy:'label'
  { value: '5', label: 'a' },
  { value: '2', label: 'C' },
  { value: '1', label: 'B' }
];
代码语言:javascript
复制
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

实现对象数组排序

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

是先获取到要比较的字段(比如上面例子中的 label)。

代码语言:javascript
复制
// 获取到 comment
const comment = context.getCommentsInside(node);
if (!comment) return;
// 获取到排序的字段
const field = comment[0] && comment[0].value && comment[0].value.split("'")[1];
if (!field) return;

拿到数组中每一项目标字段对应的值([ 'a', 'C', 'B' ])。

代码语言:javascript
复制
// 取每一项排序对象中值
let fieldValueArr = node.elements.map(item => {
  const target = (item.properties.find((prop) => {
    return prop.key.name === field
  }) || { value: '' });
  return target.value && target.value.value;
});

再对该数组进行前后顺序的检测,假如不符合我们就报错。

代码语言:javascript
复制
// 默认按照升序排序
for (let i = 1; i < fieldValueArr.length; i++) {
  let reportError = false;
  if (order === 'asc' && String(fieldValueArr[i]).localeCompare(String(fieldValueArr[i - 1])) < 0) {
    reportError = true;
  } else if (order === 'desc' && String(fieldValueArr[i]).localeCompare(String(fieldValueArr[i - 1])) > 0) {
    reportError = true;
  }
  // 判断是否是降序
  if (reportError) {
    context.report({
      node,
      message: `数组排序不正确。请根据 ${field} 字段排序`,
    });
    break;
  }
}

总结

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

参考

  • ESLint 工作原理探讨[9]
  • 自定义 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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端杂货铺 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • ESLint 是什么?
  • ESlint 的原理
  • 自定义 ESlint 插件
    • 如何做到只检测部分代码?
      • 实现对象数组排序
      • 总结
      • 参考
        • 参考资料
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档