Posted in

我写了一个 ESLint 插件,解决了团队棘手问题 – 掘金_AI阅读总结 — 包阅AI

包阅导读总结

1. `ESLint 插件`、`开发`、`代码风格`、`最佳实践`、`一致性`

2. 本文介绍如何开发自定义 ESLint 插件来保障团队代码风格和最佳实践的一致性。通过实例讲解了插件创建、规则编写、单元测试、集成使用及提供推荐配置的方法,强调其对提高代码质量的作用。

3.

– 背景

– 团队多人开发,难保证最佳实践一致性,代码审查有滞后和质量问题,ESLint 可解决。

– 准备工作

– 介绍背景知识,要解决 typeof 和 instanceof 判断变量类型的问题。

– 安装 Yeoman 和 ESLint 模板,创建目录并初始化插件。

– 插件开发

– `type-typeof-limit`插件:判断使用 `typeof` 操作符的问题,介绍规则元数据和创建函数。

– `type-instanceof-limit`插件:校验 `instanceof` 操作符的使用。

– 推荐配置:提供插件推荐配置,简化用户配置。

– 总结

– 探讨通过自定义 ESLint 插件保障代码风格统一,提高代码质量。

思维导图:

文章地址:https://juejin.cn/post/7393503669474705418

文章来源:juejin.cn

作者:颜海镜

发布时间:2024/7/21 15:00

语言:中文

总字数:3287字

预计阅读时间:14分钟

评分:84分

标签:ESLint,代码规范,前端开发,最佳实践,自动化工具


以下为原文内容

本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com

本文为稀土掘金技术社区首发签约文章,30 天内禁止转载,30 天后未获授权禁止转载,侵权必究!

在工作中,我们会沉淀了一些最佳实践,一般工作中的一个项目可能由多个开发者负责,在这样的背景下,很难做到每个开发者对最佳实践都了然于胸,在遇到人员更迭的时候,就更难受了。

虽然可以通过代码审查发现问题,但是代码审查存在两个问题:一个是滞后问题,代码审查时已经开发完了;另一个是靠人来审查,难以 100%保证质量。

如何保障团队代码风格,最佳实践的一致性,让代码看起来就是一个人写的,本身就是一个难题。

如果能有一个智能助手,实时提示代码中的哪些部分可以使用工具库中的函数代替就好了。对于前端来说,这个助理就是 ESLint。

那么如何让 ESLint 支持我们的最佳实践呢,这可以通过 ESLint 的自定义插件来实现。要开发 ESLint 插件需要一些知识储备和经验,本文我将通过一个实例来教大家如何写一个 ESLint 插件。

本文通过一个真实案例来讲解,这个案例来源于我的实际工作,通过编写 ESLint 插件,并集成到项目,帮我解决了最佳实践的推广落地问题。

背景知识

下面先来介绍下背景知识,在我之前的文章《破解面试题,在 JavaScript 中如何判断变量类型?》中,提到过使用 typeof 和 instanceof 来判断变量类型是存在很多坑的,不信你看下面的例子,如果你对这个问题感兴趣,欢迎阅读我上面的文章:

typeof []; typeof {}; [] instanceof Array [] instanceof Object 

下面将写两个 ESLint 插件来解决对上面两个问题的校验。

插件

创建 ESLint 插件,ESLint 推荐使用 Yeoman generator。首先需要安装 Yeoman,安装命令如下:

$ npm i -g yo

Yeoman 是一款通用的初始化工具,想要初始化 ESLint 插件,需要安装 ESLint 模板,安装命令如下:

$ npm i -g generator-eslint

接下来,新建一个目录,目录名字按照自己喜好就行,命令如下:

$ mkdir eslint-plugin-utils

切换到上面新建的目录,执行“yo eslint:plugin”命令会进入交互界面,询问作者、插件名字等,输入如图所示的内容即可。

image.png

稍等片刻即可完成自动初始化,初始化成功后的目录结构如下所示。其中,lib/rules 目录存放自定义规则,tests/lib/rules 目录存放规则对应的单元测试代码。

.├── .eslintrc.js├── README.md├── lib│ ├── index.js│ └── rules├── package-lock.json├── package.json└── tests└── lib └── rules

ESLint 推荐使用测试驱动开发,要求每个规则都有完整的单元测试。

type-typeof-limit

使用 typeof 操作符判断一个变量为对象时可能存在问题,如下面的 3 行代码都返回 true:

typeof {} === 'object';typeof [] === 'object';typeof null === 'object';

下面写一个新规则,当发现“typeof * === ‘object’”时给出报错提示。首先使用“yo eslint:rule”命令新建一个规则,在询问界面中输入如图所示的内容。

image.png

完成上述操作后,会生成两个文件,分别是 lib/rules/type-typeof-limit.js 和 tests/lib/
rules/type-typeof-limit.js。打开前一个文件,其内容如下:

module.exports = {meta: {type: null, docs: {description: 'typeof不能用于对象和数组,请使用@jsmini/type',category: 'Fill me in',recommended: false,url: null, },fixable: null, schema: [], },create(context) {return {};},};

其中,meta 是规则的元数据,这里需要关注的字段的含义如下,更多字段可以查看 ESLint 官网。

  • type:规则的类型,problem 代表报错,这里需要将 type 的值修改为 problem。
  • docs:存放规则文档信息。
    • description:指定规则的简短描述,需要填写。
    • category:指定规则的分类信息,包括 Possible Errors、Best Practices、Variables 等,这里可以填入 Best Practices。
  • fixable:表示这个规则是否提供自动修复功能,当其值被设置为 true 时,还需要提供自动修复的代码。

create 函数里面是具体的逻辑,其返回一个对象,该对象的属性名表示节点类型,在向下遍历树时,当遍历到和属性名匹配的节点时,ESLint 会调用属性名对应的函数。例如,我们要写的这个规则的 create 函数如下,其含义是每次遇到 BinaryExpression 节点,都会调用传递给 BinaryExpression 属性的函数。

module.exports = {create(context) {return {BinaryExpression: (node) => {},};},};

现在读者可能还不理解 BinaryExpression 的含义,这里需要介绍 ESLint 的原理。ESLint 会将每个 JavaScript 文件解析为抽象语法树(AbstractSyntax Tree,AST),简称语法树。ESLint 官网提供了一款工具,可以查看指定代码解析后的 AST。例如,下面的代码:

typeof a === 'object';

ESLint 解析上述代码后会返回一个嵌套的 AST,每个节点中的 type 属性表示当前节点的类型,观察下面的 AST,上面的判断表达式可以用下面的逻辑来判断:

  • BinaryExpression 节点。
  • left.operator 为 typeof。
  • operator 为===或==。
  • right 为 Literal,并且 value 为 object。

ESLint 会把 JavaScript 代码解析为 AST,该 AST 使用 JSON 格式表示的代码如下:

{"type": "Program","start": 0,"end": 21,"body": [{"type": "ExpressionStatement","start": 0,"end": 21,"expression": {"type": "BinaryExpression","start": 0,"end": 21,"left": {"type": "UnaryExpression","start": 0,"end": 8,"operator": "typeof","prefix": true,"argument": {"type": "Identifier","start": 7,"end": 8,"name": "a"}},"operator": "===","right": {"type": "Literal","start": 13,"end": 21,"value": "object","raw": "'object'"}}}],"sourceType": "module"}

ESLint 遍历到 BinaryExpression 节点后会执行传递给 BinaryExpression 属性的函数,并将 BinaryExpression 节点传递给这个函数,然后进行上面的逻辑判断,如果为 true,则使用 context.report 报告错误。示例代码如下:

module.exports = {create(context) {return {BinaryExpression: (node) => {const operator = node.operator;const left = node.left;const right = node.right;if ((operator === '==' || operator === '===') &&left.type === 'UnaryExpression' &&left.operator === 'typeof' &&right.type === 'Literal' &&right.value === 'object') {context.report({node,message: 'typeof不能用于对象和数组,请使用 @jsmini/type',});}},};},};

前面提到了 ESLint 推荐使用测试驱动开发,上面的代码可以通过写单元测试来快速验证结果,修改 tests/lib/rules/type-typeof-limit.js 文件中的内容如下,其中包括三个单元测试:一个合法的单元测试和两个非法的单元测试。

const rule = require('../../../lib/rules/type-typeof-limit'),RuleTester = require('eslint').RuleTester;const msg = 'typeof不能用于对象和数组,请使用@jsmini/type';const ruleTester = new RuleTester();ruleTester.run('type-typeof-limit', rule, {valid: [{ code: 'typeof a == "number"' }, { code: 'a == "object"' }],invalid: [{code: 'typeof a == "object"',errors: [{message: msg,},],},{code: 'typeof a === "object"',errors: [{message: msg,},],},],});

写好单元测试后,执行“npm test”命令即可运行测试,如果看到如图所示的输出,则表示单元测试通过了。

image.png

下面在真实实验环境下新建插件,由于我们的插件还没有发布,因此需要通过 link 的方式使用。

首先在插件目录下执行如下命令,这会将本地的插件链接到本地的 npm 全局目录。

$ npm link

新建一个空项目 eslint-plugin-utils-demo,并初始化 ESLint 配置,接下来,在 eslint-
plugin-utils-demo 根目录下执行下面的命令,这会在 node_modules 目录下创建一个软链接。

$ npm link @jsmini/eslint-plugin-utils

接下来,修改 eslint-plugin-utils-demo 根目录下的.eslintrc.js 文件,添加如下代码:

module.exports = {plugins: ['@jsmini/utils'],rules: {'@jsmini/utils/type-typeof-limit': 2,},};

在本地新建一个 xxx.js 文件,并在该文件中输入如下代码:

typeof a === 'object';

如果能够看到如图所示的红色波浪线,当将鼠标指针悬停到波浪线上时,显示如图所示的错误信息,则表示成功了。

image.png

type-instanceof-limit

参考上面 type-typeof-limit 插件的内容,可以实现校验如下的代码:

a instanceof Object;

新建一个名字为 type-instanceof-limit 的插件,这部分就不再展开介绍了,该插件的核心代码如下:

module.exports = {create(context) {function check(node) {const operator = node.operator;if (operator === 'instanceof') {context.report({node,message: 'instanceof操作符可能存在问题,请使用@jsmini/type',});}}return {BinaryExpression: check,};},};

recommended

现在已经有 2 个规则了,随着规则的增多,需要用户手动修改 rules。ESLint 配置示例如下:

module.exports = {plugins: ['@jsmini/utils'],rules: {'@jsmini/utils/type-typeof-limit': 2,'@jsmini/utils/type-instanceof-limit': 2,},};

其实插件可以提供推荐的配置,类似 eslint:recommended,用户直接使用推荐的配置即可。修改 lib/index.js 文件中的 exports,添加 configs 配置,示例代码如下:

module.exports = {rules: requireIndex(__dirname + '/rules'),configs: {plugins: ['@jsmini/utils'],rules: {'@jsmini/utils/type-typeof-limit': 'error','@jsmini/utils/type-instanceof-limit': 'error',},},};

接下来,用户就可以直接像下面这样使用,而不需要单独配置 plugins 和 rules 了。

module.exports = {extends: ['@jsmini/utils:recommended'],};

总结

在本文中,我们探讨了如何通过编写自定义 ESLint 插件来保障团队代码风格和最佳实践的一致性。我们首先介绍了 ESLint 插件开发的背景知识,然后详细讲解了如何使用 Yeoman generator 创建 ESLint 插件。通过具体案例,我们编写了两个 ESLint 插件:type-typeof-limittype-instanceof-limit,并展示了如何在项目中集成和测试这些插件。此外,我们还讨论了如何为插件提供推荐配置,以简化用户的配置过程。

通过这些步骤,我们可以确保团队代码风格统一,减少代码审查中的问题,提高代码质量。

欢迎大家阅读和分享这篇文章,如果有任何疑问或建议,欢迎在评论区留言。感谢大家的支持和阅读!