乐趣区

自定义-ESLint-规则让代码持续美丽

60 篇原创好文~
本文首发于政采云前端团队博客:自定义 ESLint 规定,让代码继续漂亮

背景

一段实在的代码倒退历史

很久很久以前,有一个需要,而后产出了一段代码,代码优雅而简洁

export const getConfig = (param1, param2) => {return ...};

不久又来了个需要,加个参数扩大,so easy!

export const getConfig = (param1, param2, param3) => {return ...};

通过屡次产品需要迭代后,当初的代码

export const getConfig = (param1, param2, param3, param4, param5, param6, param7……) => {return ...};

在产品迭代过程中,下面的 case 一个函数的参数从 2 个倒退到了 7 个,优雅的代码逐步变为不可保护。
这是什么问题?这归咎于日益增长的需要,疾速响应和代码品质之间的矛盾。

那如何防止呢?

  • 制订代码标准
  • 靠开发同学的自我涵养
  • 进行 Code Review
  • 工具提醒
  • 发版管制,不容许发版

制订代码标准必定是须要的,那如何束缚代码呢?标准文档宣讲,再凭借开发同学的自我涵养?答案是:无奈保障。

Code Review?但不免也有落网之鱼。发版管制?能无效解决然而开发体验不好。

如果咱们在开发者写代码的时候就及时给到提醒和倡议,那开发体验就很棒了,而 ESLint 的自定义规定就能够实现在开发过程中给开发同学敌对的提醒。

ESLint 原理

ESLint 是一个代码查看工具,通过动态的剖析,寻找有问题的模式或者代码。默认应用 Espree 解析器将代码解析为 AST 形象语法树,而后再对代码进行查看。

看下最简略的一段代码应用 espree 解析器转换成的形象语法树结构,此处能够应用 astexplorer 疾速不便查看解析成 AST 的构造:

代码片段:

var a = 1;

转换出的后果:

{
  "type": "Program",
  "start": 0,
  "end": 10,
  "range": [
    0,
    10
  ],
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 10,
      "range": [
        0,
        10
      ],
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 9,
          "range": [
            4,
            9
          ],
          "id": {
            "type": "Identifier",
            "start": 4,
            "end": 5,
            "range": [
              4,
              5
            ],
            "name": "a"
          },
          "init": {
            "type": "Literal",
            "start": 8,
            "end": 9,
            "range": [
              8,
              9
            ],
            "value": 1,
            "raw": "1"
          }
        }
      ],
      "kind": "var"
    }
  ],
  "sourceType": "module"
}

代码转换为 AST 后,能够很不便的对代码的每个节点对代码进行查看。

自定义 ESLint 规定开发

怎么自定义

语法树剖析

对指标代码进行语法树解析,可应用 astexplorer

编写规定

上面是一个规定简略的构造(官网 API 文档阐明)

module.exports = {
  meta: {
    docs: {description: "最多参数容许参数",},
  },
  create: function (context) {
    return {FunctionDeclaration: (node) => {if (node.params.length > 3) {
          context.report({
            node,
            message: "参数最多不能超过 3 个",
          });
        }
      },
    };
  },
};
  • meta(对象)蕴含规定的元数据
  • create (function) 返回一个对象,其中蕴含了 ESLint 在遍历 JavaScript 代码的形象语法树 AST (ESTree 定义的 AST) 时,用来拜访节点的办法
  • context.report () 用来公布正告或谬误,并能提供主动修复性能(取决于你所应用的配置)

最简略的示例(只应用 node 和 message 参数):

context.report({
    node,
    message: "参数最多不能超过 3 个",
});

应用下面的这个规定,联合编辑器就有了对整个 node 节点的提醒,如果须要更准确的谬误或正告提醒,咱们能够应用 loc 参数,API 文档阐明。

如何应用自定义规定

应用自定义的 ESLint 规定,你须要自定义一个 ESLint 的插件,而后将规定写到自定义的 ESLint 插件中,而后在业务代码中增加 ESLint 配置,引入 ESLint 插件。

ESLint 插件

创立

创立一个 ESLint plugin,并创立 一个 ESLint rule

基于 Yeoman generator,能够疾速创立 ESLint plugin 我的项目。

npm i -g yo
npm i -g generator-eslint
// 创立一个 plugin
yo eslint:plugin
// 创立一个规定
yo eslint:rule

创立好的我的项目目录构造:

  • rules 文件夹寄存的是各个规定文件
  • tests 文件夹寄存单元测试文件
  • package.json 是你的 ESLint 插件 npm 包的阐明文件,其中的 name 属性就是你的 ESLint 插件的名称,命名规定:带前缀 eslint-plugin-

示例代码:

lib/rules/max-params.js

module.exports = {
  meta: {
    docs: {description: "最多参数",},
  },
  create: function (context) {
    /**
     * 获取函数的参数的开始、完结地位
     * @param {node} node AST Node 
     */
    function getFunctionParamsLoc(node) {
      const paramsLength = node.params.length;
      return {start: node.params[0].loc.start,
        end: node.params[paramsLength - 1].loc.end,
      };
    }
    return {FunctionDeclaration: (node) => {if (node.params.length > 3) {
          context.report({loc: getFunctionParamsLoc(node),
            node,
            message: "参数最多不能超过 3 个",
          });
        }
      },
    };
  },
};

补充测试用例

/tests/lib/rules/max-params.js

var ruleTester = new RuleTester();
ruleTester.run("max-params", rule, {valid: ["function test(d, e, f) {}"],
  invalid: [
    {code: "function test(a, b, c, d) {}",
        errors: [{message: "参数最多不能超过 3 个",}]
    },
  ],
});

ESLint 插件装置

在须要的业务代码中装置你的 ESLint 插件。(eslint-plugin-my-eslist-plugin 是你的 ESLint 插件 npm 包的包名)

npm install eslint-plugin-my-eslist-plugin 

如果你的 npm 包还未公布,须要进行本地调试:

可应用 npm link 本地调试,npm link 的应用。

配置

增加你的 plugin 包名(eslint-plugin- 前缀可疏忽)到 .eslintrc 配置文件的 plugins 字段。

.eslintrc 配置文件示例:

{
    "plugins": ["zoo" // 你的 ESLint plugin 的名字]
}

rules 中再将 plugin 中的规定导入。
⚠️ ESLint 更新后,须要重启 vscode,能力失效。(vscode 重启快捷方式:CTRL + SHITF + P,输出 Reload Window

此处波及 ESLint 的规定设置(参考阐明)

{
    "rules": {"zoo/rule-name": 2}
}

成果

理论利用案例

函数、办法的入参个数管制,其实曾经在 ESLint 的规定中了。在业务场景中,咱们须要对咱们的业务规定编写自定义的 ESLint 规定。

一个简略的业务场景:业务中通常会呈现跳转到很多不同的业务域名的操作,不同的环境有不同的域名,咱们须要从配置中取出域名应用,而不是采取硬编码域名的计划。

由此咱们产生出了一个规定:禁止硬编码业务域名。

规定为:

module.exports = {
  meta: {
    type: "suggestion",
    docs: {description: "不容许硬编码业务域名",},
    fixable: "code",
  },

  create: function (context) {const sourceCode = context.getSourceCode();

    function checkDomain(node) {
      // 匹配硬编码的业务域名的正则
      const Reg = /^(http:\/\/|https:\/\/|\/\/)(.*.){0,1}zcygov(.com|cn)(.*)/;
      const content =
        (node.type === "Literal" && node.value) ||
        (node.type === "TemplateLiteral" && node.quasis[0].value.cooked);

      const domainNode =
        (node.type === "Literal" && node) ||
        (node.type === "TemplateLiteral" && node.quasis[0]);

      if (Reg.test(content)) {
        context.report({
          node,
          // 谬误 / 正告提示信息
          message: "不容许硬编码业务域名",
          // 修复
          fix(fixer) {const fixes = [];
            
            let domainKey = content.match(Reg)[2];
            domainKey = domainKey
              ? domainKey.substr(0, domainKey.length - 1)
              : "";

            if (node.type === "Literal") {
              fixes.push(
                fixer.replaceTextRange([domainNode.start + 1, domainNode.end - 1],
                  content.replace(Reg, `$4`)
                )
              );
            }

            if (node.type === "TemplateLiteral") {
              fixes.push(
                fixer.replaceTextRange([domainNode.start, domainNode.end],
                  content.replace(Reg, `$4`)
                )
              );
            }
             
            if (
              node.type === "Literal" &&
              node.parent.type === "JSXAttribute"
            ) {fixes.push(fixer.insertTextBefore(node, "{"));
              fixes.push(fixer.insertTextAfter(node, "}"));
            }

            fixes.push(
              fixer.insertTextBefore(
                node,
                `window.getDomain('${domainKey}') + `
              )
            );

            return fixes;
          },
        });
      }
    }
    return {
      // 文本
      Literal: checkDomain,
      // 模板字符串
      TemplateLiteral: checkDomain,
    };
  },
};

补充测试用例

/tests/lib/rules/no-zcy-domain.js

var rule = require("../../../lib/rules/no-zcy-domain"),
    RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester();
ruleTester.run("no-zcy-domain", rule, {
  valid: [
    "bar",
    "baz",
    `
  var s = {x: "zcygov"};
  `,
  ],
  invalid: [
    {
      code: `
              var s = "//zcygov.cn"
            `,
      errors: [
        {message: "不容许硬编码业务域名",},
      ],
    },
    {
      code: `
            var s = {x: "http://bidding.zcygov.cn"};
            `,
      errors: [
        {message: "不容许硬编码业务域名",},
      ],
    },
  ],
});

联合 vscode 保留主动修复 ESLint 谬误的性能,成果如下:

更多的利用场景

除了下面说的硬编码的场景,还能够将积淀出的最佳实际和业务标准通过自定义 ESLint 的形式来提醒开发者,这对于多人帮助、代码保护、代码格调的一致性都会有很大的帮忙。

更多的利用场景有:

  • Input 必须要有 maxlength 属性,避免申请的后端接口数据库异样
  • 代码中不能呈现加减乘除等计算,如果须要计算应该引入工具函数,来管制因为前端浮点数计算引起的 Bug
  • 标准限度,单位元的两边的括号要用英文括号,不能用中文括号,来达到交互展现对立的成果
  • 代码中不能应用 OSS 地址的动态资源门路,应该应用 CDN 地址的资源门路

参考文献

  • https://developer.mozilla.org…
  • https://eslint.org/docs/devel…

招贤纳士

政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。

如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com

退出移动版