<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    手把手教你 ESLint

    共 13295字,需瀏覽 27分鐘

     ·

    2020-09-10 16:23

    前言

    小沈是一個(gè)剛剛開(kāi)始工作的前端實(shí)習(xí)生,第一次進(jìn)行團(tuán)隊(duì)開(kāi)發(fā),難免有些緊張。在導(dǎo)師的安排下,拿到了項(xiàng)目的 git 權(quán)限,開(kāi)始進(jìn)行 clone。

    $?git?clone[email protected]:company/project.git

    小沈開(kāi)始細(xì)細(xì)品味著同事們的代碼,終于在他的不懈努力下,發(fā)現(xiàn)了老王 2 年前寫的一個(gè) bug,跟導(dǎo)師報(bào)備之后,小沈開(kāi)始著手修改。年輕人嘛,容易沖動(dòng),不僅修復(fù)了老王的 bug,還把這部分代碼進(jìn)行了重構(gòu),使用了前兩天剛剛從書里學(xué)會(huì)的策略模式,去掉了一些不必要 if else 邏輯。小沈?yàn)t灑的摸了摸自己稀疏的頭發(fā),得意的準(zhǔn)備提交代碼,想著第一天剛來(lái)就秀了下自己的超強(qiáng)的編碼能力。接下來(lái)可怕的事情發(fā)生了,代碼死活不能通過(guò) lint 工具的檢測(cè),急得他面紅耳赤,趕緊跑去問(wèn)導(dǎo)師,導(dǎo)師告訴他,只要按照控制臺(tái)的 warning 修改代碼就好。小沈反駁道,這個(gè) lint 工具非讓我去掉分號(hào),我在學(xué)校的時(shí)候,老師就教我分號(hào)是必不可少的,沒(méi)有分號(hào)的代碼是不完美的。導(dǎo)師無(wú)奈的笑了笑,打開(kāi)了小沈的實(shí)習(xí)評(píng)分表,在團(tuán)隊(duì)合作一項(xiàng)中勾選『較差』。

    不服氣的小沈,寫了一篇博客發(fā)布到了 CSDN 上,還收獲了不少閱讀量。

    image

    問(wèn):工作第一天小沈犯了哪些錯(cuò)誤?

    1. 對(duì)不了解的業(yè)務(wù)代碼進(jìn)行重構(gòu),這是業(yè)務(wù)開(kāi)發(fā)的大忌;
    2. 沒(méi)有遵守團(tuán)隊(duì)規(guī)范,團(tuán)隊(duì)開(kāi)發(fā)帶有太強(qiáng)的個(gè)人情緒;
    3. 上面都是我編的,聽(tīng)說(shuō)現(xiàn)在寫文章開(kāi)頭都要編個(gè)故事。

    lint 工具簡(jiǎn)史

    在計(jì)算機(jī)科學(xué)中,lint是一種工具的名稱,它用來(lái)標(biāo)記代碼中,某些可疑的、不具結(jié)構(gòu)性(可能造成bug)的語(yǔ)句。它是一種靜態(tài)程序分析工具,最早適用于C語(yǔ)言,在UNIX平臺(tái)上開(kāi)發(fā)出來(lái)。后來(lái)它成為通用術(shù)語(yǔ),可用于描述在任何一種編程語(yǔ)言中,用來(lái)標(biāo)記代碼中有疑義語(yǔ)句的工具。? ?-- by wikipedia

    在 JavaScript 20 多年的發(fā)展歷程中,也出現(xiàn)過(guò)許許多多的 lint 工具,下面就來(lái)介紹下主流的三款 lint 工具。

    1. JSLint
    2. JSHint
    3. ESLint
    image

    JSLint

    JSLint logo

    JSLint 可以說(shuō)是最早出現(xiàn)的 JavaScript 的 lint 工具,由 Douglas Crockford (《JavaScript 語(yǔ)言精粹》作者) 開(kāi)發(fā)。從《JavaScript 語(yǔ)言精粹》的筆風(fēng)就能看出,Douglas 是個(gè)眼里容不得瑕疵的人,所以 JSLint 也繼承了這個(gè)特色,JSLint 的所有規(guī)則都是由 Douglas 自己定義的,可以說(shuō)這是一個(gè)極具 Douglas 個(gè)人風(fēng)格的 lint 工具,如果你要使用它,就必須接受它所有規(guī)則。值得稱贊的是,JSLint 依然在更新,而且也提供了 node 版本:node-jslint。

    JSHint

    JSHint logo

    由于 JSLint 讓很多人無(wú)法忍受它的規(guī)則,感覺(jué)受到了壓迫,所以 Anton Kovalyov (現(xiàn)在在 Medium 工作) 基于 JSLint 開(kāi)發(fā)了 JSHint,。JSHint 在 JSLint 的基礎(chǔ)上提供了豐富的配置項(xiàng),給了開(kāi)發(fā)者極大的自由,JSHint 一開(kāi)始就保持著開(kāi)源軟件的風(fēng)格,由社區(qū)進(jìn)行驅(qū)動(dòng),發(fā)展十分迅速。早期?jQuery 也是使用 JSHint 進(jìn)行代碼檢查的,不過(guò)現(xiàn)在已經(jīng)轉(zhuǎn)移到 ESLint 了。

    ESLint

    ESLint logo

    ESLint 由 Nicholas C. Zakas (《JavaScript 高級(jí)程序設(shè)計(jì)》作者) 于2013年6月創(chuàng)建,它的出現(xiàn)因?yàn)?Zakas 想使用 JSHint 添加一條自定義的規(guī)則,但是發(fā)現(xiàn) JSHint 不支持,于是乎自己開(kāi)發(fā)了一個(gè)。

    ESLint 號(hào)稱下一代的 JS Linter 工具,它的靈感來(lái)源于 PHP Linter,將源代碼解析成 AST,然后檢測(cè) AST 來(lái)判斷代碼是否符合規(guī)則。ESLint 使用 esprima 將源代碼解析成 AST,然后你就可以使用任意規(guī)則來(lái)檢測(cè) AST 是否符合預(yù)期,這也是 ESLint 高可擴(kuò)展性的原因。

    早期源碼:

    var?ast?=?esprima.parse(text,?{?loc:?true,?range:?true?}),
    ????walk?=?astw(ast);

    walk(function(node)?{
    ????api.emit(node.type,?node);
    });

    return?messages;

    但是,那個(gè)時(shí)候 ESLint 并沒(méi)有大火,因?yàn)樾枰獙⒃创a轉(zhuǎn)成 AST,運(yùn)行速度上輸給了 JSHint ,并且 JSHint 當(dāng)時(shí)已經(jīng)有完善的生態(tài)(編輯器的支持)。真正讓 ESLint 大火是因?yàn)?ES6 的出現(xiàn)。

    ES6 發(fā)布后,因?yàn)樾略隽撕芏嗾Z(yǔ)法,JSHint 短期內(nèi)無(wú)法提供支持,而 ESLint 只需要有合適的解析器就能夠進(jìn)行 lint 檢查。這時(shí) babel 為 ESLint 提供了支持,開(kāi)發(fā)了 babel-eslint,讓ESLint 成為最快支持 ES6 語(yǔ)法的 lint 工具。

    谷歌趨勢(shì)

    在 2016 年,ESLint整合了與它同時(shí)誕生的另一個(gè) lint 工具:JSCS,因?yàn)樗c ESLint 具有異曲同工之妙,都是通過(guò)生成 AST 的方式進(jìn)行規(guī)則檢測(cè)。

    ESLint整合JSCS

    自此,ESLint 在 JS Linter 領(lǐng)域一統(tǒng)江湖,成為前端界的主流工具。

    Lint 工具的意義

    下面一起來(lái)思考一個(gè)問(wèn)題:Lint 工具對(duì)工程師來(lái)說(shuō)到底是代碼質(zhì)量的保證還是一種束縛?

    然后,我們?cè)倏纯?ESLint 官網(wǎng)的簡(jiǎn)介:

    代碼檢查是一種靜態(tài)的分析,常用于尋找有問(wèn)題的模式或者代碼,并且不依賴于具體的編碼風(fēng)格。對(duì)大多數(shù)編程語(yǔ)言來(lái)說(shuō)都會(huì)有代碼檢查,一般來(lái)說(shuō)編譯程序會(huì)內(nèi)置檢查工具。

    JavaScript 是一個(gè)動(dòng)態(tài)的弱類型語(yǔ)言,在開(kāi)發(fā)中比較容易出錯(cuò)。因?yàn)闆](méi)有編譯程序,為了尋找 JavaScript 代碼錯(cuò)誤通常需要在執(zhí)行過(guò)程中不斷調(diào)試。像 ESLint 這樣的可以讓程序員在編碼的過(guò)程中發(fā)現(xiàn)問(wèn)題而不是在執(zhí)行的過(guò)程中。

    因?yàn)?JavaScript 這門神奇的語(yǔ)言,在帶給我們靈活性的同時(shí),也埋下了一些坑。比如 == 涉及到的弱類型轉(zhuǎn)換,著實(shí)讓人很苦惱,還有 this 的指向,也是一個(gè)讓人迷惑的東西。而 Lint 工具就很好的解決了這個(gè)問(wèn)題,干脆禁止你使用 == ,這種做法雖然限制了語(yǔ)言的靈活性,但是帶來(lái)的收益也是可觀的。

    還有就是作為一門動(dòng)態(tài)語(yǔ)言,因?yàn)槿鄙倬幾g過(guò)程,有些本可以在編譯過(guò)程中發(fā)現(xiàn)的錯(cuò)誤,只能等到運(yùn)行才發(fā)現(xiàn),這給我們調(diào)試工作增加了一些負(fù)擔(dān),而 Lint 工具相當(dāng)于為語(yǔ)言增加了編譯過(guò)程,在代碼運(yùn)行前進(jìn)行靜態(tài)分析找到出錯(cuò)的地方。

    所以匯總一下,Lint工具的優(yōu)勢(shì):

    1. 避免低級(jí)bug,找出可能發(fā)生的語(yǔ)法錯(cuò)誤

    使用未聲明變量、修改 const 變量……

    2. 提示刪除多余的代碼

    聲明而未使用的變量、重復(fù)的 case ……

    3. 確保代碼遵循最佳實(shí)踐

    可參考 airbnb style、javascript standard

    4. 統(tǒng)一團(tuán)隊(duì)的代碼風(fēng)格

    加不加分號(hào)?使用 tab 還是空格?

    使用方式

    說(shuō)了那么多,還是來(lái)看下有點(diǎn)實(shí)際意義的,ESLint 到底是如何使用的。

    初始化

    如果想在現(xiàn)有項(xiàng)目中引入 ESLint,可以直接運(yùn)行下面的命令:

    #?全局安裝?ESLint
    $?npm?install?-g?eslint

    #?進(jìn)入項(xiàng)目
    $?cd?~/Code/ESLint-demo

    #?初始化?package.json
    $?npm?init?-f

    #?初始化?ESLint?配置
    $?eslint?--init
    image

    在使用 eslint --init 后,會(huì)出現(xiàn)很多用戶配置項(xiàng),具體可以參考:eslint cli 部分的源碼。

    經(jīng)過(guò)一系列一問(wèn)一答的環(huán)節(jié)后,你會(huì)發(fā)現(xiàn)在你文件夾的根目錄生成了一個(gè) .eslintrc.js 文件。

    image

    配置方式

    ESLint 一共有兩種配置方式:

    1. 使用注釋把 lint 規(guī)則直接嵌入到源代碼中

    這是最簡(jiǎn)單粗暴的方式,直接在源代碼中使用 ESLint 能夠識(shí)別的注釋方式,進(jìn)行 lint 規(guī)則的定義。

    /*?eslint?eqeqeq:?"error"?*/
    var?num?=?1
    num?==?'1'
    image

    當(dāng)然我們一般使用注釋是為了臨時(shí)禁止某些嚴(yán)格的 lint 規(guī)則出現(xiàn)的警告:

    /*?eslint-disable?*/
    alert('該注釋放在文件頂部,整個(gè)文件都不會(huì)出現(xiàn)?lint?警告')

    /*?eslint-enable?*/
    alert('重新啟用?lint?告警')

    /*?eslint-disable?eqeqeq?*/
    alert('只禁止某一個(gè)或多個(gè)規(guī)則')

    /*?eslint-disable-next-line?*/
    alert('當(dāng)前行禁止?lint?警告')

    alert('當(dāng)前行禁止?lint?警告')?//?eslint-disable-line

    2. 使用配置文件進(jìn)行 lint 規(guī)則配置

    在初始化過(guò)程中,有一個(gè)選項(xiàng)就是使用什么文件類型進(jìn)行 lint 配置(What format do you want your config file to be in?):

    {
    ????type:?"list",
    ????name:?"format",
    ????message:?"What?format?do?you?want?your?config?file?to?be?in?",
    ????default:?"JavaScript",
    ????choices:?["JavaScript",?"YAML",?"JSON"]
    }

    官方一共提供了三個(gè)選項(xiàng):

    1. JavaScript (eslintrc.js)
    2. YAML (eslintrc.yaml)
    3. JSON (eslintrc.json)

    另外,你也可以自己在 package.json 文件中添加 eslintConfig 字段進(jìn)行配置。

    翻閱 ESLint 源碼可以看到,其配置文件的優(yōu)先級(jí)如下:

    const?configFilenames?=?[
    ??".eslintrc.js",
    ??".eslintrc.yaml",
    ??".eslintrc.yml",
    ??".eslintrc.json",
    ??".eslintrc",
    ??"package.json"
    ];

    當(dāng)然你也可以使用 cli 自己指定配置文件路徑:

    image

    項(xiàng)目級(jí)與目錄級(jí)的配置

    我們有如下目錄結(jié)構(gòu),此時(shí)在根目錄運(yùn)行 ESLint,那么我們將得到兩個(gè)配置文件 .eslintrc.js(項(xiàng)目級(jí)配置) 和 src/.eslintrc.js(目錄級(jí)配置),這兩個(gè)配置文件會(huì)進(jìn)行合并,但是 src/.eslintrc.js 具有更高的優(yōu)先級(jí)。

    目錄結(jié)構(gòu)

    但是,我們只要在 src/.eslintrc.js 中配置 "root": true,那么 ESLint 就會(huì)認(rèn)為 src 目錄為根目錄,不再向上查找配置。

    {
    ??"root":?true
    }

    配置參數(shù)

    下面我們一起來(lái)細(xì)細(xì)品味 ESLinte 的配置規(guī)則。

    解析器配置

    {
    ??//?解析器類型
    ??//?espima(默認(rèn)),?babel-eslint,?@typescript-eslint/parse
    ??"parse":?"esprima",
    ??//?解析器配置參數(shù)
    ??"parseOptions":?{
    ????//?代碼類型:script(默認(rèn)), module
    ????"sourceType":?"script",
    ????// es 版本號(hào),默認(rèn)為 5,也可以使用年份,比如 2015 (同 6)
    ????"ecamVersion":?6,
    ????//?es?特性配置
    ????"ecmaFeatures":?{
    ????????"globalReturn":?true,?//?允許在全局作用域下使用?return?語(yǔ)句
    ????????"impliedStrict":?true,?//?啟用全局?strict?mode?
    ????????"jsx":?true?//?啟用?JSX
    ????},
    ??}
    }

    對(duì)于 @typescript-eslint/parse 這個(gè)解析器,主要是為了替代之前存在的 TSLint,TS 團(tuán)隊(duì)因?yàn)?ESLint 生態(tài)的繁榮,且 ESLint 具有更多的配置項(xiàng),不得不拋棄 TSLint 轉(zhuǎn)而實(shí)現(xiàn)一個(gè) ESLint 的解析器。同時(shí),該解析器擁有不同的配置:

    {
    ??"parserOptions":?{
    ????"ecmaFeatures":?{
    ??????"jsx":?true
    ????},
    ????"useJSXTextNode":?true,
    ????"project":?"./tsconfig.json",
    ????"tsconfigRootDir":?"../../",
    ????"extraFileExtensions":?[".vue"]
    ??}
    }

    環(huán)境與全局變量

    ESLint 會(huì)檢測(cè)未聲明的變量,并發(fā)出警告,但是有些變量是我們引入的庫(kù)聲明的,這里就需要提前在配置中聲明。

    {
    ??"globals":?{
    ????//?聲明?jQuery?對(duì)象為全局變量
    ????"$":?false?//?true表示該變量為?writeable,而?false?表示?readonly
    ??}
    }

    globals 中一個(gè)個(gè)的進(jìn)行聲明未免有點(diǎn)繁瑣,這個(gè)時(shí)候就需要使用到 env ,這是對(duì)一個(gè)環(huán)境定義的一組全局變量的預(yù)設(shè)(類似于 babel 的 presets)。

    {
    ??"env":?{
    ????"amd":?true,
    ????"commonjs":?true,
    ????"jquery":?true
    ??}
    }

    可選的環(huán)境很多,預(yù)設(shè)值都在這個(gè)文件中進(jìn)行定義,查看源碼可以發(fā)現(xiàn),其預(yù)設(shè)變量都引用自 globals 包。

    env
    env

    規(guī)則設(shè)置

    ESLint 附帶有大量的規(guī)則,你可以在配置文件的 rules 屬性中配置你想要的規(guī)則。每一條規(guī)則接受一個(gè)參數(shù),參數(shù)的值如下:

    • "off" 或 0:關(guān)閉規(guī)則
    • "warn" 或 1:開(kāi)啟規(guī)則,warn 級(jí)別的錯(cuò)誤 (不會(huì)導(dǎo)致程序退出)
    • "error" 或 2:開(kāi)啟規(guī)則,error級(jí)別的錯(cuò)誤(當(dāng)被觸發(fā)的時(shí)候,程序會(huì)退出)

    舉個(gè)例子,我們先寫一段使用了平等(equality)的代碼,然后對(duì) eqeqeq 規(guī)則分別進(jìn)行不同的配置。

    //?demo.js
    var?num?=?1
    num?==?'1'
    eqeqeq 規(guī)則校驗(yàn)

    這里使用了命令行的配置方式,如果你只想對(duì)單個(gè)文件進(jìn)行某個(gè)規(guī)則的校驗(yàn)就可以使用這種方式。

    但是,事情往往沒(méi)有我們想象中那么簡(jiǎn)單,ESLint 的規(guī)則不僅只有關(guān)閉和開(kāi)啟這么簡(jiǎn)單,每一條規(guī)則還有自己的配置項(xiàng)。如果需要對(duì)某個(gè)規(guī)則進(jìn)行配置,就需要使用數(shù)組形式的參數(shù)。

    我們看下 quotes 規(guī)則,根據(jù)官網(wǎng)介紹,它支持字符串和對(duì)象兩個(gè)配置項(xiàng)。

    quotes
    {
    ??"rules":?{
    ????//?使用數(shù)組形式,對(duì)規(guī)則進(jìn)行配置
    ????//?第一個(gè)參數(shù)為是否啟用規(guī)則
    ????//?后面的參數(shù)才是規(guī)則的配置項(xiàng)
    ????"quotes":?[
    ??????"error",
    ??????"single",
    ??????{
    ????????"avoidEscape":?true?
    ??????}
    ????]
    ??}
    }

    根據(jù)上面的規(guī)則:

    //?bad
    var?str?=?"test?'ESLint'?rule"

    //?good
    var?str?=?'test?"ESLint"?rule'

    擴(kuò)展

    擴(kuò)展就是直接使用別人已經(jīng)寫好的 lint 規(guī)則,方便快捷。擴(kuò)展一般支持三種類型:

    {
    ??"extends":?[
    ????"eslint:recommended",
    ????"plugin:react/recommended",
    ????"eslint-config-standard",
    ??]
    }
    • eslint: 開(kāi)頭的是 ESLint 官方的擴(kuò)展,一共有兩個(gè):eslint:recommendedeslint:all。
    • plugin: 開(kāi)頭的是擴(kuò)展是插件類型,也可以直接在 plugins 屬性中進(jìn)行設(shè)置,后面一節(jié)會(huì)詳細(xì)講到。
    • 最后一種擴(kuò)展來(lái)自 npm 包,官方規(guī)定 npm 包的擴(kuò)展必須以 eslint-config- 開(kāi)頭,使用時(shí)可以省略這個(gè)頭,上面案例中 eslint-config-standard 可以直接簡(jiǎn)寫成 standard。

    如果你覺(jué)得自己的配置十分滿意,也可以將自己的 lint 配置發(fā)布到 npm 包,只要將包名命名為 eslint-config-xxx 即可,同時(shí),需要在 package.json 的 peerDependencies 字段中聲明你依賴的 ESLint 的版本號(hào)。

    插件

    使用插件

    雖然官方提供了上百種的規(guī)則可供選擇,但是這還不夠,因?yàn)楣俜降囊?guī)則只能檢查標(biāo)準(zhǔn)的 JavaScript 語(yǔ)法,如果你寫的是 JSX 或者 Vue 單文件組件,ESLint 的規(guī)則就開(kāi)始束手無(wú)策了。

    這個(gè)時(shí)候就需要安裝 ESLint 的插件,來(lái)定制一些特定的規(guī)則進(jìn)行檢查。ESLint 的插件與擴(kuò)展一樣有固定的命名格式,以 eslint-plugin- 開(kāi)頭,使用的時(shí)候也可以省略這個(gè)頭。

    npm?install?--save-dev?eslint-plugin-vue?eslint-plugin-react
    {
    ??"plugins":?[
    ????"react",?//?eslint-plugin-react
    ????"vue",???//?eslint-plugin-vue
    ??]
    }

    或者是在擴(kuò)展中引入插件,前面有提到 plugin: 開(kāi)頭的是擴(kuò)展是進(jìn)行插件的加載。

    {
    ??"extends":?[
    ????"plugin:react/recommended",
    ??]
    }

    通過(guò)擴(kuò)展的方式加載插件的規(guī)則如下:

    extPlugin?=?`plugin:${pluginName}/${configName}`

    對(duì)照上面的案例,插件名(pluginName) 為 react,也就是之前安裝 eslint-plugin-react 包,配置名(configName)為 recommended。那么這個(gè)配置名又是從哪里來(lái)的呢?

    可以看到 eslint-plugin-react 的源碼。

    module.exports?=?{
    ??//?自定義的?rule
    ??rules:?allRules,
    ??//?可用的擴(kuò)展
    ??configs:?{
    ????//?plugin:react/recommended
    ????recomended:?{
    ??????plugins:?[?'react'?]
    ??????rules:?{...}
    ????},
    ????//?plugin:react/all
    ????all:?{
    ??????plugins:?[?'react'?]
    ??????rules:?{...}
    ????}
    ??}
    }

    配置名是插件配置的 configs 屬性定義的,這里的配置其實(shí)就是 ESLint 的擴(kuò)展,通過(guò)這種方式即可以加載插件,又可以加載擴(kuò)展。

    開(kāi)發(fā)插件

    ESLint 官方為了方便開(kāi)發(fā)者,提供了 Yeoman 的模板(generator-eslint)。

    #?安裝模塊
    npm?install?-g?yo?generator-eslint

    #?創(chuàng)建目錄
    mkdir?eslint-plugin-demo
    cd?eslint-plugin-demo

    #?創(chuàng)建模板
    yo?eslint:plugin
    eslint:plugin
    eslint:plugin 目錄

    創(chuàng)建好項(xiàng)目之后,就可以開(kāi)始創(chuàng)建一條規(guī)則了,幸運(yùn)的是 generator-eslint 除了能夠生成插件的模板代碼外,還具有創(chuàng)建規(guī)則的模板代碼。打開(kāi)之前創(chuàng)建的 eslint-plugin-demo 文件夾,在該目錄下添加一條規(guī)則,我希望這條規(guī)則能檢測(cè)出我的代碼里面是否有 console ,所以,我給該規(guī)則命名為 disable-console。

    yo?eslint:rule
    eslint:rule
    eslint:rule 目錄

    接下來(lái)我們看看如何來(lái)指定 ESLinte 的一個(gè)規(guī)則:

    打開(kāi) lib/rules/disable-console.js ,可以看到默認(rèn)的模板代碼如下:

    module.exports?=?{
    ??meta:?{
    ????docs:?{
    ??????description:?"disable?console",
    ??????category:?"Fill?me?in",
    ??????recommended:?false
    ????},
    ????schema:?[]
    ??},
    ??create:?function?(context)?{
    ????//?variables?should?be?defined?here
    ????return?{
    ??????//?give?me?methods
    ????};
    ??}
    };

    簡(jiǎn)單的介紹下其中的參數(shù)(更詳細(xì)的介紹可以查看官方文檔):

    • meta:規(guī)則的一些描述信息
      • descrition(string):規(guī)則的簡(jiǎn)短描述
      • category(string):規(guī)則的類別(具體類別可以查看官網(wǎng))
      • recommended(boolean):是否加入 eslint:recommended
      • docs:規(guī)則的描述對(duì)象
      • schema(array):規(guī)則所接受的配置項(xiàng)
    • create:返回一個(gè)對(duì)象,該對(duì)象包含 ESLint 在遍歷 JavaScript 代碼 AST 時(shí),所觸發(fā)的一系列事件勾子。

    在詳細(xì)講解如何創(chuàng)建一個(gè)規(guī)則之前,我們先來(lái)談?wù)?AST(抽象語(yǔ)法樹(shù))。ESLint 使用了一個(gè)叫做 Espree 的 JavaScript 解析器來(lái)把 JavaScript 代碼解析為一個(gè) AST 。然后深度遍歷 AST,每條規(guī)則都會(huì)對(duì)匹配的過(guò)程進(jìn)行監(jiān)聽(tīng),每當(dāng)匹配到一個(gè)類型,相應(yīng)的規(guī)則就會(huì)進(jìn)行檢查。為了方便查看 AST 的各個(gè)節(jié)點(diǎn)類型,這里提供一個(gè)網(wǎng)站能十分清晰的查看一段代碼解析成 AST 之后的樣子:astexplorer。如果你想找到所有 AST 節(jié)點(diǎn)的類型,可以查看 estree。

    astexplorer
    astexplorer

    可以看到 console.log() 屬于 ExpressionStatement(表達(dá)式語(yǔ)句) 中的 CallExpression(調(diào)用語(yǔ)句)。

    {
    ??"type":?"ExpressionStatement",
    ??"expression":?{
    ????"type":?"CallExpression",
    ????"callee":?{
    ??????"type":?"MemberExpression",
    ??????"object":?{
    ????????"type":?"Identifier",
    ????????"name":?"console"
    ??????},
    ??????"property":?{
    ????????"type":?"Identifier",
    ????????"name":?"log"
    ??????},
    ??????"computed":?false
    ????}
    ??}
    }

    所以,我們要判斷代碼中是否調(diào)用了 console,可以在 create 方法返回的對(duì)象中,寫一個(gè) CallExpression 方法,在 ESLint 遍歷 AST 的過(guò)程中,對(duì)調(diào)用語(yǔ)句進(jìn)行監(jiān)聽(tīng),然后檢查該調(diào)用語(yǔ)句是否為 console 調(diào)用。

    module.exports?=?{
    ??create:?function(context)?{
    ????return?{
    ??????CallExpression(node)?{
    ????????//?獲取調(diào)用語(yǔ)句的調(diào)用對(duì)象
    ????????const?callObj?=?node.callee.object
    ????????if?(!callObj)?{
    ??????????return
    ????????}
    ????????if?(callObj.name?!==?'console')?{
    ??????????//?如果調(diào)用對(duì)象為?console,通知?ESLint
    ??????????context.report({
    ????????????node,
    ????????????message:?'error:?should?remove?console'
    ??????????})
    ????????}
    ??????},
    ????}
    ??}
    }

    可以看到我們最后通過(guò) context.report 方法,告訴 ESLint 這是一段有問(wèn)的代碼,具體要怎么處理,就要看 ESLint 配置中,該條規(guī)則是 [off, warn, error] 中的哪一個(gè)了。

    之前介紹規(guī)則的時(shí)候,有講到規(guī)則是可以接受配置的,下面看看我們自己制定規(guī)則的時(shí)候,要如何接受配置項(xiàng)。其實(shí)很簡(jiǎn)單,只需要在 mate 對(duì)象的 schema 中定義好參數(shù)的類型,然后在 create 方法中,通過(guò) context.options 獲取即可。下面對(duì) disable-console 進(jìn)行修改,畢竟禁止所有的 console 太過(guò)嚴(yán)格,我們可以添加一個(gè)參數(shù),該參數(shù)是一個(gè)數(shù)組,表示允許調(diào)用的 console 方法。

    module.exports?=?{
    ??meta:?{
    ????docs:?{
    ??????description:?"disable?console",?//?規(guī)則描述
    ??????category:?"Possible?Errors",????//?規(guī)則類別
    ??????recommended:?false
    ????},
    ????schema:?[?//?接受一個(gè)參數(shù)
    ??????{
    ????????type:?'array',?//?接受參數(shù)類型為數(shù)組
    ????????items:?{
    ??????????type:?'string'?//?數(shù)組的每一項(xiàng)為一個(gè)字符串
    ????????}
    ??????}
    ????]
    ??},

    ??create:?function(context)?{
    ????const?logs?=?[?//?console?的所以方法
    ????????"debug",?"error",?"info",?"log",?"warn",?
    ????????"dir",?"dirxml",?"table",?"trace",?
    ????????"group",?"groupCollapsed",?"groupEnd",?
    ????????"clear",?"count",?"countReset",?"assert",?
    ????????"profile",?"profileEnd",?
    ????????"time",?"timeLog",?"timeEnd",?"timeStamp",?
    ????????"context",?"memory"
    ????]
    ????return?{
    ??????CallExpression(node)?{
    ?????????//?接受的參數(shù)
    ????????const?allowLogs?=?context.options[0]
    ????????const?disableLogs?=?Array.isArray(allowLogs)
    ??????????//?過(guò)濾掉允許調(diào)用的方法
    ????????????logs.filter(log?=>?!allowLogs.includes(log))
    ??????????:?logs
    ????????const?callObj?=?node.callee.object
    ????????const?callProp?=?node.callee.property
    ????????if?(!callObj?||?!callProp)?{
    ??????????return
    ????????}
    ????????if?(callObj.name?!==?'console')?{
    ??????????return
    ????????}
    ????????//?檢測(cè)掉不允許調(diào)用的?console?方法
    ????????if?(disableLogs.includes(callProp.name))?{
    ??????????context.report({
    ????????????node,
    ????????????message:?'error:?should?remove?console'
    ??????????})
    ????????}
    ??????},
    ????}
    ??}
    }

    規(guī)則寫完之后,打開(kāi) tests/rules/disable-console.js ,編寫測(cè)試用例。

    var?rule?=?require("../../../lib/rules/disable-console")
    var?RuleTester?=?require("eslint").RuleTester

    var?ruleTester?=?new?RuleTester()
    ruleTester.run("disable-console",?rule,?{
    ??valid:?[{
    ????code:?"console.info(test)",
    ????options:?[['info']]
    ??}],
    ??invalid:?[{
    ????code:?"console.log(test)",
    ????errors:?[{?message:?"error:?should?remove?console"?}]
    ??}]
    });
    test

    最后,只需要引入插件,然后開(kāi)啟規(guī)則即可。

    //?eslintrc.js
    module.exports?=?{
    ??plugins:?[?'demo'?],
    ??rules:?{
    ????'demo/disable-console':?[
    ??????'error',?[?'info'?]
    ????],
    ??}
    }
    use plugin demo

    最佳配置

    最佳配置

    業(yè)界有許多 JavaScript 的推薦編碼規(guī)范,較為出名的就是下面兩個(gè):

    1. airbnb style
    2. javascript standard

    同時(shí)這里也推薦 AlloyTeam 的 eslint-config-alloy。

    但是代碼規(guī)范這個(gè)東西,最好是團(tuán)隊(duì)成員之間一起來(lái)制定,確保大家都能夠接受,如果實(shí)在是有人有異議,就只能少數(shù)服從多數(shù)了。雖然這節(jié)的標(biāo)題叫最佳配置,但是軟件行業(yè)并有沒(méi)有什么方案是最佳方案,即使 javascript standard 也不是 javascript 標(biāo)準(zhǔn)的編碼規(guī)范,它僅僅只是叫這個(gè)名字而已,只有適合的才是最好的。

    最后安利一下,將 ESLint 和 Prettier 結(jié)合使用,不僅統(tǒng)一編碼規(guī)范,也能統(tǒng)一代碼風(fēng)格。具體實(shí)踐方式,請(qǐng)參考我的文章:使用ESLint+Prettier來(lái)統(tǒng)一前端代碼風(fēng)格。

    總結(jié)

    看到這里我們做一個(gè)總結(jié),JavaScript 的 linter 工具發(fā)展歷史其實(shí)也不算短,ESLint 之所以能夠后來(lái)者居上,主要原因還是 JSLint 和 JSHint 采用自頂向下的方式來(lái)解析代碼,并且早期 JavaScript 語(yǔ)法萬(wàn)年不更新,能這種方式夠以較快的速度來(lái)解析代碼,找到可能存在的語(yǔ)法錯(cuò)誤和不規(guī)范的代碼。但是 ES6 發(fā)布之后,JavaScript 語(yǔ)法發(fā)生了很多的改動(dòng),比如:箭頭函數(shù)、模板字符串、擴(kuò)展運(yùn)算符……,這些語(yǔ)法的發(fā)布,導(dǎo)致 JSLint 和 JSHint 如果不更新解析器就沒(méi)法檢測(cè) ES6 的代碼。而 ESLint 另辟蹊徑,采用 AST 的方式對(duì)代碼進(jìn)行靜態(tài)分析,并保留了強(qiáng)大的可擴(kuò)展性和靈活的配置能力。這也告訴我們,在日常的編碼過(guò)程中,一定要考慮到后續(xù)的擴(kuò)展能力。

    正是因?yàn)檫@個(gè)強(qiáng)大擴(kuò)展能力,讓業(yè)界的很多 JavaScript 編碼規(guī)范能夠在各個(gè)團(tuán)隊(duì)進(jìn)行快速的落地,并且團(tuán)隊(duì)自己定制的代碼規(guī)范也可以對(duì)外共享。

    最后,希望你通過(guò)上面的學(xué)習(xí)已經(jīng)理解了 ESLint 帶來(lái)的好處,同時(shí)掌握了 ESLint 的用法,并可以為現(xiàn)有的項(xiàng)目引入 ESLint 改善項(xiàng)目的代碼質(zhì)量。

    參考

    • ESLint 官網(wǎng)
    • JS Linter 進(jìn)化史
    • ESLint 工作原理探討




    推薦閱讀




    我的公眾號(hào)能帶來(lái)什么價(jià)值?(文末有送書規(guī)則,一定要看)

    每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)

    為什么現(xiàn)在面試總是面試造火箭?

    瀏覽 58
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    操逼视频a片 | 99免费看 | 日韩一区二区精品视频 | AAA一级黄片 | 日韩无码一卡二卡 |