自定義 ESLint 規則,讓代碼持續美麗

60 篇原創好文~
本文首發於政採雲前端團隊博客:自定義 ESLint 規則,讓代碼持續美麗
javascript

背景

一段真實的代碼發展歷史

好久好久之前,有一個需求,而後產出了一段代碼,代碼優雅而簡潔前端

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

不久又來了個需求,加個參數擴展,so easy!java

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

通過屢次產品需求迭代後,如今的代碼imagenode

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

在產品迭代過程當中,上面的 case 一個函數的參數從 2 個發展到了 7 個,優雅的代碼逐漸變爲不可維護。
這是什麼問題?這歸咎於日益增加的需求,快速響應和代碼質量之間的矛盾。git

那如何避免呢?github

  • 制定代碼規範
  • 靠開發同窗的自我修養
  • 進行 Code Review
  • 工具提示
  • 發版控制,不容許發版

制定代碼規範確定是須要的,那如何約束代碼呢?規範文檔宣講,再憑藉開發同窗的自我修養?答案是:沒法保證。數據庫

Code Review ?但不免也有落網之魚。發版控制?能有效解決可是開發體驗很差。npm

若是咱們在開發者寫代碼的時候就及時給到提示和建議,那開發體驗就很棒了,而 ESLint 的自定義規則就能夠實如今開發過程當中給開發同窗友好的提示。json

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 文檔說明

image

如何使用自定義規則

使用自定義的 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
    }
}

效果

image

實際應用案例

函數、方法的入參個數控制,其實已經在 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 地址的資源路徑
  • ...

參考文獻

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索