为团队写个eslint插件

1,142 阅读3分钟

相信每个团队都有一套代码规范,但遵循规范全靠自觉,等codereveiw再指出就浪费团队精力了,最好的就是在开发时就能有提醒,这就需要eslint出场了。虽然eslint已经提供了丰富的内置规则和插件生态,但有时还是需要开发自己团队的规则集。让我们来开发一个微信小程序相关的。

eslint-plugin-ahaha-miniprogram

插件名就是这个,一个插件包含一组规则,这次开发这样一个规则:no-native-api,帮助我们禁用一些原生api, 比如不能使用同步api, 如果统一使用miniprogram-api-promise,那么需要禁止使用wx.xxx

规则分析

禁用什么api需要能配置,而且需要能定制提示信息,比如提示disallow use of native api: wx.navigateTo, use wxp.navigateTo instead, 如果不提供,默认:disallow use of native api: wx.navigateTo。所以最终的配置格式是这样:

{
  "rules": {
    "ahaha-miniprogram/no-native-api": [
      "error",
      {
        "getStorageSync": true, // equal 'disallow use of native api: wx.getStorageSync'
        "navigateTo": "disallow use of native api: wx.navigateTo, use wxp.navigateTo instead" // custom message
      }
    ]
  }
}

启动项目

官方已经提供了脚手架,直接安装:

$ npm install -g yo
$ npm install -g generator-eslint
$ mkdir eslint-plugin-ahaha-miniprogram && cd eslint-plugin-ahaha-miniprogram
$ yo eslint

然后按提示填写即可 需要其他工程配置的可以使用:generator-libs

开发规则

lib/rules下创建no-native-api.js

描述规则

按我们定义的规则配置加入以下描述性内容:

module.exports = {
  meta: {
    type: 'problem', // problem | suggestion | layout
    docs: {
      description: 'Disallow Use of some native api: wx.xxx',
      category: 'Best Practices',
      recommended: true,
    },
    schema: [ // eslint使用jsonschema:https://json-schema.org/understanding-json-schema 做配置校验
      {
        type: 'object',
        minProperties: 1,
        additionalProperties: {
          oneOf: [
            { type: 'string', minLength: 1 }, // 如果是string类型是自定义提示信息
            { type: 'boolean', enum: [true] }, // 如果是boolean类型则是是否检测该api
          ],
        },
      },
    ],
    messages: { forbid: 'disallow use of native api: wx.{{api}}' }, // 消息定义,方便单元测试和代码内复用,不然会重复
  },
};

实现规则

规则的实现使用create(context){},返回的是一个遍历ast节点的对象,和babel插件的visit一样。

先来验证参数,未配置则跳过:

const forbiddenApis = context.options[0] || {};
if (!Object.keys(forbiddenApis).length) {
  return {};
}

然后找目标ast节点,wx.xxxMemberExpression类型(使用ast神奇astexplorer查看):

return {
  MemberExpression(node) {},
};

继续判断是不是目标ast节点:

if (node.object.name !== 'wx') {
  // 不是wx.xxx
  return;
}

然后根据我们的配置格式,如果api无限制,则跳过:

const message = forbiddenApis[node.property.name];
if (!message) {
  return;
}

最后就是使用了限制的api,则要报错:

// https://cn.eslint.org/docs/developer-guide/working-with-rules#contextreport
context.report({
  node,
  message: message === true ? undefined : message, // 如果是自定义提示信息则使用 message 字段
  messageId: message === true ? 'forbid' : undefined, // 如果是默认提示信息则使用 messageId 字段,值是描述规则的 messages 里的一项
  // messages 可以使用模板语法,这是传递的数据
  data: {
    api: node.property.name, 
  },
});

完整代码:

create(context) {
const forbiddenApis = context.options[0] || {};
if (!Object.keys(forbiddenApis).length) {
  return {};
}

return {
  MemberExpression(node) {
    if (node.object.name !== 'wx') {
      return;
    }

    const message = forbiddenApis[node.property.name];
    if (!message) {
      return;
    }

    context.report({
      node,
      message: message === true ? undefined : message,
      messageId: message === true ? 'forbid' : undefined,
      data: {
        api: node.property.name,
      },
    });
  },
};
},

单元测试

好了,代码撸完了,要看看是否可行了,加个单元测试看看,eslint已经提供了单元测试功能,十分省事。

新建tests/lib/rules/no-native-api.js,加入以下代码:

const { RuleTester } = require('eslint');
const rule = require('../../../lib/rules/no-native-api');

const ruleTester = new RuleTester({
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
  },
});

ruleTester.run('no-native-api', rule, {
  // 什么样的是对的
  valid: [
    {
      code: `wx.getStorageSync()`,
      options: [{ navigateBack: true }],
    },
    // 其他
  ],
  // 什么样的是错误的
  invalid: [
    {
      code: `wx.getStorageSync()`,
      options: [{ getStorageSync: true }],
      errors: [{ messageId: 'forbid' }],
    },
    // 其他
  ],
});

然后 npm test 即可

发布

如果要使用需要发布到npm,然后在项目中安装。使用npm publish发布即可,发布前要检查包名是否已被占用,否则会报错。

总结

到这里一个基本的eslint插件就完成了,可以根据团队代码规范逐步添加规则,完整项目请查看:eslint-plugin-ahaha-miniprogram

人总是不靠谱的,交给计算机才是王道,有了自己的eslint插件那治理团队代码就犹如烹小鲜了,嗷呜。