Posted in

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

包阅导读总结

1.

关键词:ESLint 插件、最佳实践、代码风格、单元测试、团队代码

2.

总结:本文介绍作者通过写 ESLint 插件解决团队代码最佳实践的推广落地问题。阐述了插件开发的背景知识、创建流程,包括安装工具、新建规则等,并通过具体案例讲解规则编写、单元测试及在项目中的集成,还提到提供推荐配置以简化用户配置,强调了保障团队代码风格统一和提高代码质量。

3.

主要内容:

– 工作背景

– 团队项目开发人员对最佳实践难以统一掌握,代码审查有滞后和质量难保证问题。

– ESLint 插件

– 介绍背景知识,如 typeof 和 instanceof 判断变量类型存在的问题。

– 插件创建流程

– 安装 Yeoman 和 ESLint 模板。

– 新建目录并初始化。

– 案例展示

– type-typeof-limit 规则:解决 typeof 操作符判断对象的问题。

– type-instanceof-limit 规则:校验特定代码。

– 推荐配置:简化用户配置过程。

– 总结

– 探讨通过 ESLint 插件保障团队代码风格和最佳实践一致性,展示开发、集成、测试过程,提高代码质量。

思维导图:

文章地址:https://mp.weixin.qq.com/s/yllZHdN8rmxeNFw7b_baGQ

文章来源:mp.weixin.qq.com

作者:颜海镜

发布时间:2024/7/22 2:56

语言:中文

总字数:2995字

预计阅读时间:12分钟

评分:78分

标签:ESLint,插件开发,代码质量,JavaScript,团队协作


以下为原文内容

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

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

点击关注公众号,“技术干货”及时达!

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

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

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

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

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

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

背景知识

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

typeof[];//'object'
typeof{};//'object'typeofc;//'object'

[]instanceofArray//true
[]instanceofObject//true注意这里

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

插件

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

$npmi-gyo

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

$npmi-ggenerator-eslint

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

$mkdireslint-plugin-utils

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

稍等片刻即可完成自动初始化,初始化成功后的目录结构如下所示。其中,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';
typeofnull==='object';

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

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

module.exports={
meta:{
type:null,//`problem`,`suggestion`,or`layout`
docs:{
description:'typeof不能用于对象和数组,请使用@jsmini/type',
category:'Fillmein',
recommended:false,
url:null,//URLtothedocumentationpageforthisrule
},
fixable:null,//Or`code`or`whitespace`
schema:[],//Addaschemaiftherulehasoptions
},

create(context){
return{
//visitorfunctionsfordifferenttypesofnodes
};
},
};

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

  • type:规则的类型,problem 代表报错,这里需要将 type 的值修改为 problem。
    • 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 文件解析为抽象语法树(Abstract Syntax Tree,AST),简称语法树。ESLint 官网提供了一款工具,可以查看指定代码解析后的 AST。例如,下面的代码:

typeofa==='object';

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

  • 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)=>{
constoperator=node.operator;
constleft=node.left;
constright=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 文件中的内容如下,其中包括三个单元测试:一个合法的单元测试和两个非法的单元测试。

construle=require('../../../lib/rules/type-typeof-limit'),
RuleTester=require('eslint').RuleTester;

constmsg='typeof不能用于对象和数组,请使用@jsmini/type';

construleTester=newRuleTester();
ruleTester.run('type-typeof-limit',rule,{
valid:[{code:'typeofa=="number"'},{code:'a=="object"'}],

invalid:[
{
code:'typeofa=="object"',
errors:[
{
message:msg,
},
],
},
{
code:'typeofa==="object"',
errors:[
{
message:msg,
},
],
},
],
});

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

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

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

$npmlink

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

$npmlink@jsmini/eslint-plugin-utils

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

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

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

typeofa==='object';

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

type-instanceof-limit

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

ainstanceofObject;

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

module.exports={
create(context){
functioncheck(node){
constoperator=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,并展示了如何在项目中集成和测试这些插件。此外,我们还讨论了如何为插件提供推荐配置,以简化用户的配置过程。

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

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

点击关注公众号,“技术干货”及时达!