前言

很久没有写文章了,主要是没什么值得写的。
打工人天天搬砖,确实写无可写。
直到最近整理团队规范,总算遇到了值得一写的内容。

遇见

整理代码的时候,在项目中发现了很多定时器,比如:

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


陈其文
429 声望19 粉丝

前端