前言
很久没有写文章了,主要是没什么值得写的。
打工人天天搬砖,确实写无可写。
直到最近整理团队规范,总算遇到了值得一写的内容。
遇见
整理代码的时候,在项目中发现了很多定时器,比如:
setInterval(() => {
this.getData()
}, 1e4)
这是定时轮询,更新数据的一段代码。翻了翻历史记录,是个新来的实习生写的。
因为没有把定时器存储起来,自然不可能取消。
所以每次打开这个组件,就会产生一个定时器,如果用户多次进入这个页面,那就很糟糕了。
解决
全局查找setInterval
一个个处理,其实问题也不大。我找了下,大概也就百十来个setInterval
!当然不会是每个都需要解决,但是逐个查看解决,也有点燥人。
所以最后的解决方案是,通过eslint查找,然后统一解决。最主要的是,避免后面的代码继续出现这种情况。
尝试
网上有很多现成的demo,但是信息大多比较散乱。比如有的demo版本比较低,虽然可以运行,项目引用就不行。
有的demo不完整,还需要自己补全。
当然,最主要的还是,我压根没了解过eslint的运行规则……
不得已,查资料,debugger,一步步前进。
AST抽象语法树
eslint怎么运行?这就不得不接触到AST的概念。
代码拆解,从语句,语句块,表达式,函数,声明,等等概念,全部拆解到树形结构上,一一映射。
在这篇文章上已经讲得比较细致:AST抽象语法树.
eslint-plugin
eslint-plugin有自己的规范。
我们要开发一个插件,就必须遵循规范。
恰恰是在这个规范上卡了我比较长时间——整整一天。
我找了相关的demo,自己写了一个例子,查找没有存储的定时器。很快例子就写好了,也通过测试。
于是我就开开心心的发到npm,然后在项目中引用,运行代码,一切都很顺利。
但是什么提示都没有!!!
黑人脸!!!
debugger
在node_modules中调试代码,继续运行。
但是更加奇怪,连我打的debugger都没执行。
不断修改,再运行,还是没有。
如此数次下来,渐渐怀疑人生。
难道是.eslintrc.js
配置不对?
天知道,我压根没读过.eslintrc.js
的配置,我只是一个搬运工。
好吧,先把.eslintrc.js
读一遍。
原来,我真的没有配对.eslintrc.js
。。。
一番折腾之后,终于有了提示 definition for rule 'no-record-time' was not found
。
好吧,no-record-time
是我引入的npm包,继续折腾。
因为开发方式的原因,我需要用以下方式引入:
plugins: [
"no-record-time",
],
rules: {
'no-record-time/no-record-time': 2
},
解决了这步,信心大增。
然后,我又发现了一个命令npm run lint --debug
,这个命令可以直接把eslint运行的结果打印出来,我终于不用改一句代码就重启一次编辑器,如虎添翼。
但是,结果很奇怪,我写的规则突然不生效了。
怎么肥事?
Parser API
开始我用的是Program:exit
查找,这是Parser API提供的一个对象,eslint的规范可以返回这个属性,执行所有Program节点的代码遍历。
在测试中,我写的规则可以被很好的运行。但是到了项目中,就不行了。
原因未知。
但是我发现了一个更好的对象——ExpressionStatement
。
这是一个表达式语句对象。
如上文:
setInterval(() => {
this.getData()
}, 1e4)
这就是一个表达式语句。
如果这样就不是表达式了:
let timer = setInterval(() => {
this.getData()
}, 1e4)
这是一个声明赋值语句。
所以可以直接查找表达式语句对象。
一通操作,把节点对象打印之后,发现了规律。node.expression.callee.name
这个属性可以读取表达式的函数名,当然,前提是必须有node.expression.callee
。
那么判断条件就很容易了:
function getSetInterval(node) {
if (node.expression.callee && node.expression.callee.name === 'setInterval') {
return false
}
if (node.expression.callee && node.expression.callee.name === 'setTimeout') {
return false
}
return true
}
这样就可以找到没存储的定时器了。
完整代码
/**
* @fileoverview Rule to flag timer didn't record.
* @author 陈其文<jianwang19@sina.com>
* @copyright 2021-01 All rights reserved.
*/
"use strict";
module.exports = {
meta: {
type: "problem",
docs: {
description: "定时器检测",
category: "ExpressionStatement",
recommended: true,
},
schema: []
},
create: function (context) {
function getSetInterval(node) {
if (node.expression.callee && node.expression.callee.name === 'setInterval') {
return false
}
if (node.expression.callee && node.expression.callee.name === 'setTimeout') {
return false
}
return true
}
return {
ExpressionStatement(node){
let state = getSetInterval(node)
if (!state) {
context.report(node, '{{ name }} timer didn\'t record', {
name: node.expression.callee.name
});
}
},
};
},
};
最后发现,其实很简单。
还没经过完整测试,也许存在bug。
但终究是迈出了一大步。
git仓库如下:eslint-plugin-no-record-time
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。