<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          自定義 ESLint 規(guī)則,讓代碼持續(xù)美麗

          共 8245字,需瀏覽 17分鐘

           ·

          2020-12-28 10:43

          背景

          一段真實的代碼發(fā)展歷史

          很久很久以前,有一個需求,然后產(chǎn)出了一段代碼,代碼優(yōu)雅而簡潔

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

          不久又來了個需求,加個參數(shù)擴展,so easy!

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

          經(jīng)過多次產(chǎn)品需求迭代后,現(xiàn)在的代碼

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

          在產(chǎn)品迭代過程中,上面的 case 一個函數(shù)的參數(shù)從 2 個發(fā)展到了 7 個,優(yōu)雅的代碼逐漸變?yōu)椴豢删S護。這是什么問題?這歸咎于日益增長的需求,快速響應(yīng)和代碼質(zhì)量之間的矛盾。

          那如何避免呢?

          • 制定代碼規(guī)范

          • 靠開發(fā)同學(xué)的自我修養(yǎng)

          • 進行 Code Review

          • 工具提示

          • 發(fā)版控制,不允許發(fā)版

          制定代碼規(guī)范肯定是需要的,那如何約束代碼呢?規(guī)范文檔宣講,再憑借開發(fā)同學(xué)的自我修養(yǎng)?答案是:無法保證。

          Code Review ?但難免也有落網(wǎng)之魚。發(fā)版控制?能有效解決但是開發(fā)體驗不好。

          如果我們在開發(fā)者寫代碼的時候就及時給到提示和建議,那開發(fā)體驗就很棒了,而 ESLint 的自定義規(guī)則就可以實現(xiàn)在開發(fā)過程中給開發(fā)同學(xué)友好的提示。

          ESLint 原理

          ESLint 是一個代碼檢查工具,通過靜態(tài)的分析,尋找有問題的模式或者代碼。默認使用 Espree (https://github.com/eslint/espree) 解析器將代碼解析為 AST 抽象語法樹,然后再對代碼進行檢查。

          看下最簡單的一段代碼使用 espree 解析器轉(zhuǎn)換成的抽象語法樹結(jié)構(gòu),此處可以使用 astexplorer (https://astexplorer.net/) 快速方便查看解析成 AST 的結(jié)構(gòu):

          代碼片段:

          var?a?=?1;

          轉(zhuǎn)換出的結(jié)果:

          {
          ??"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"
          }

          代碼轉(zhuǎn)換為 AST 后,可以很方便的對代碼的每個節(jié)點對代碼進行檢查。

          自定義 ESLint 規(guī)則開發(fā)

          怎么自定義

          語法樹分析

          對目標代碼進行語法樹解析,可使用 astexplorer (https://astexplorer.net/)

          編寫規(guī)則

          下面是一個規(guī)則簡單的結(jié)構(gòu)(官方 API 文檔說明:https://eslint.org/docs/developer-guide/working-with-rules#rule-basics)

          module.exports?=?{
          ??meta:?{
          ????docs:?{
          ??????description:?"最多參數(shù)允許參數(shù)",
          ????},
          ??},
          ??create:?function?(context)?{
          ????return?{
          ??????FunctionDeclaration:?(node)?=>?{
          ????????if?(node.params.length?>?3)?{
          ??????????context.report({
          ????????????node,
          ????????????message:?"參數(shù)最多不能超過3個",
          ??????????});
          ????????}
          ??????},
          ????};
          ??},
          };
          • meta(對象)包含規(guī)則的元數(shù)據(jù)
          • create ( function ) 返回一個對象,其中包含了 ESLint 在遍歷 JavaScript 代碼的抽象語法樹 AST ( ESTree 定義的 AST ) 時,用來訪問節(jié)點的方法

          • context.report ( ) ?用來發(fā)布警告或錯誤,并能提供自動修復(fù)功能(取決于你所使用的配置)

          最簡單的示例(只使用 node 和 message 參數(shù)):

          context.report({
          ????node,
          ????message:?"參數(shù)最多不能超過3個",
          });
          使用上面的這個規(guī)則,結(jié)合編輯器就有了對整個 node 節(jié)點的提示,如果需要更精確的錯誤或警告提示,我們可以使用 loc 參數(shù),API 文檔說明 (https://eslint.org/docs/developer-guide/working-with-rules#context-report)。
          image

          如何使用自定義規(guī)則

          使用自定義的 ESLint 規(guī)則,你需要自定義一個 ESLint 的插件,然后將規(guī)則寫到自定義的 ESLint 插件中,然后在業(yè)務(wù)代碼中添加 ESLint 配置,引入 ESLint 插件。

          ESLint 插件

          創(chuàng)建

          創(chuàng)建一個 ESLint plugin,并創(chuàng)建 一個 ESLint rule

          基于 Yeoman generator (https://yeoman.io/authoring/) ,可以快速創(chuàng)建 ESLint plugin 項目。

          npm?i?-g?yo
          npm?i?-g?generator-eslint
          //?創(chuàng)建一個plugin
          yo?eslint:plugin
          //?創(chuàng)建一個規(guī)則
          yo?eslint:rule

          創(chuàng)建好的項目目錄結(jié)構(gòu):

          • rules 文件夾存放的是各個規(guī)則文件

          • tests 文件夾存放單元測試文件

          • package.json 是你的 ESLint 插件 npm 包的說明文件,其中的 name 屬性就是你的 ESLint ?插件的名稱,命名規(guī)則:帶前綴 eslint-plugin-

          示例代碼:

          lib/rules/max-params.js

          module.exports?=?{
          ??meta:?{
          ????docs:?{
          ??????description:?"最多參數(shù)",
          ????},
          ??},
          ??create:?function?(context)?{
          ????/**
          ?????*?獲取函數(shù)的參數(shù)的開始、結(jié)束位置
          ?????*?@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:?"參數(shù)最多不能超過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:?"參數(shù)最多不能超過3個",
          ????????}]
          ????},
          ??],
          });

          ESLint 插件安裝

          在需要的業(yè)務(wù)代碼中安裝你的 ESLint 插件。(eslint-plugin-my-eslist-plugin 是你的 ESLint 插件 npm 包的包名)

          npm?install?eslint-plugin-my-eslist-plugin?

          如果你的 npm 包還未發(fā)布,需要進行本地調(diào)試:

          可使用 npm link 本地調(diào)試,npm link 的使用 (https://www.baidu.com/s?ie=UTF-8&wd=npm%20link)。

          配置

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

          .eslintrc 配置文件示例:

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

          rules 中再將 plugin 中的規(guī)則導(dǎo)入。?? ESlint更新后,需要重啟 vsCode,才能生效。( vsCode ?重啟快捷方式:CTRL +SHITF + P,輸入 Reload Window

          此處涉及 ESLint 的規(guī)則設(shè)置(參考說明:https://eslint.org/docs/user-guide/configuring#configuring-rules)

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

          效果

          image

          實際應(yīng)用案例

          函數(shù)、方法的入?yún)€數(shù)控制,其實已經(jīng)在 ESLint 的規(guī)則中了。在業(yè)務(wù)場景中,我們需要對我們的業(yè)務(wù)規(guī)則編寫自定義的 ESLint 規(guī)則。

          一個簡單的業(yè)務(wù)場景:業(yè)務(wù)中通常會出現(xiàn)跳轉(zhuǎn)到很多不同的業(yè)務(wù)域名的操作,不同的環(huán)境有不同的域名,我們需要從配置中取出域名使用,而不是采取硬編碼域名的方案。

          由此我們產(chǎn)生出了一個規(guī)則:禁止硬編碼業(yè)務(wù)域名。

          規(guī)則為:

          module.exports?=?{
          ??meta:?{
          ????type:?"suggestion",
          ????docs:?{
          ??????description:?"不允許硬編碼業(yè)務(wù)域名",
          ????},
          ????fixable:?"code",
          ??},

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

          ????function?checkDomain(node)?{
          ??????//?匹配硬編碼的業(yè)務(wù)域名的正則
          ??????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:?"不允許硬編碼業(yè)務(wù)域名",
          ??????????//?修復(fù)
          ??????????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?=?"http://zcygov.cn"
          ????????????`
          ,
          ??????errors:?[
          ????????{
          ??????????message:?"不允許硬編碼業(yè)務(wù)域名",
          ????????},
          ??????],
          ????},
          ????{
          ??????code:?`
          ????????????var?s?=?{
          ??????????????x:?"http://bidding.zcygov.cn"
          ????????????};
          ????????????`
          ,
          ??????errors:?[
          ????????{
          ??????????message:?"不允許硬編碼業(yè)務(wù)域名",
          ????????},
          ??????],
          ????},
          ??],
          });
          結(jié)合 vsCode 保存自動修復(fù) ESLint 錯誤的功能,效果如下:

          更多的應(yīng)用場景

          除了上面說的硬編碼的場景,還可以將沉淀出的最佳實踐和業(yè)務(wù)規(guī)范通過自定義 ESLint 的方式來提示開發(fā)者,這對于多人協(xié)助、代碼維護、代碼風(fēng)格的一致性都會有很大的幫助。

          更多的應(yīng)用場景有:

          • Input 必須要有 maxlength 屬性,防止請求的后端接口數(shù)據(jù)庫異常

          • 代碼中不能出現(xiàn)加減乘除等計算,如果需要計算應(yīng)該引入工具函數(shù),來控制由于前端浮點數(shù)計算引起的 Bug

          • 規(guī)范限制,單位元的兩邊的括號要用英文括號,不能用中文括號,來達到交互展示統(tǒng)一的效果

          • 代碼中不能使用 OSS 地址的靜態(tài)資源路徑,應(yīng)該使用 CDN 地址的資源路徑

          • ...

          參考文獻

          • https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/SpiderMonkey/Parser_API

          • https://eslint.org/docs/developer-guide/working-with-rules

          看完兩件事

          如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我兩件小事
          1.點個「在看」,讓更多人也能看到這篇內(nèi)容(點了在看」,bug -1 ?
          2.關(guān)注公眾號「前端公蝦米」,持續(xù)為你推送精選好文


          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色视频免费观看 | 国产精品豆花视频www | 国产特级AAA精彩免费看 | 精品福利导航网 | 操女生小逼 |