前端 code lint 和代碼風格指南(內附最佳實踐)

前言

lint 工具用來檢查編程錯誤,最初是從 C 語言中發展起來的。在 C 語言最初時期,編譯器沒法捕獲一些常見的編程錯誤,所以開發出了一個叫作 lint 的輔助程序,經過掃描源文件來查找問題。javascript

當咱們在 linting 的時候咱們到底在幹什麼?實際上,最終目標是但願代碼更加健壯,而且不論團隊有多少成員,代碼就像同一我的寫出來的同樣,加強可讀性強。css

能夠將衆多 linters 的檢查目標大體分爲三類:html

  • programmer errors :主要是對語法的檢查,這類錯誤會影響程序執行的正確性。
  • best practices :其目的主要是爲了不出現讓人困惑的代碼,即便檢查出問題也不必定意味着程序會執行出錯,也有多是正確的,但依然會使人困惑。這一步是避免潛在的錯誤,以及讓代碼更加清晰明確。
  • style issues :主要是代碼風格方面的檢查,例如空格、標點符號、代碼外觀等等。

前端 linters 分類

JavaScript

下圖展現了 JavaScript linters 的進化史:前端

JSLint

2002 年由 Douglas Crockford 建立,用來進行 JavaScript 語法檢查和校驗。JSLint 定義了一個比 ECMAScript 編程語言標準更爲嚴格的子集,是一種更高的標準。JSLint 徹底是用 JavaScript 編寫的。vue

JSLint 接收 JavaScript 源代碼並對其進行掃描。若是發現問題,它將返回一條消息來描述問題以及源代碼中的大概位置。這些問題多數時候是語法錯誤,但不全是語法錯誤,也多是代碼風格和結構的問題。它不能證實程序是正確的,只是提供了一個方式來幫助發現問題。JSLint 更加關心代碼質量,所以即便瀏覽器能夠正常運行的代碼,JSLint 也可能不會經過。使用 JSLint 就意味着要欣然接受它全部的建議。java

JSLint 能夠對 JavaScript 源代碼或 JSON 文本進行操做。 JSLint 將會承認 ES6 的一部分優秀的特性,例如 letconst 等等。react

評價

優勢
  • 使用簡單,開箱即用,無需再次配置。
缺點
  • 有限的配置選項,不少規則不能禁用。
  • 規範嚴格,凡是不承認的風格都會報一個 warning 。
  • 擴展性差。
  • 沒法根據錯誤定位到對應的規則。

JSHint

2010年基於 JSLint 誕生了 JSHint ,它主要解決了 JSLint 過於獨斷的問題,提供了一些配置以及添加一些 rules 。相較之下更友好,也更容易配置,因此很快就發展了起來,也獲得了衆多 IDE 和編輯器的支持。git

JSHint 掃描用 JavaScript 編寫的程序,並報告常見的錯誤和潛在的錯誤。 潛在的問題多是語法錯誤、因爲隱式類型轉換致使的錯誤、變量泄漏等。能夠經過指定任意數量的 linting 選項或在源代碼中聲明指令來控制 JSHint 的行爲。es6

JSHint 附帶了一組默認的警告,但這些也是可配置的。能夠在配置文件中指定要打開或關閉的 JSHint 選項。 例如,如下文件將啓用有關未定義和未使用的變量的警告,並告知 JSHint 一個名爲 MY_GLOBAL 的全局變量。github

{
  "undef": true,
  "unused": true,
  "globals": {
    "MY_GLOBAL": true
  }
}
複製代碼

可是,因爲它是基於 JSLint 開發的,天然原有的一些問題它也繼承下來了,好比不易擴展,不容易直接根據報錯定位到具體的規則配置等。

評價

優勢
  • 能夠靈活配置規則,支持配置文件
  • 支持了一些經常使用類庫
  • 支持了基本的 ES6 語法
缺點
  • 不支持自定義規則
  • 沒法根據錯誤定位到對應的規則

ESLint

2013年,Nicholas C. Zakas 建立了 ESLint ,提供了更好的 es6 支持,以及更多的 rules ,尤爲是一些代碼風格方面的,以及一個靈活的插件系統,可讓開發者建立本身的 rules ,同時能夠方便的根據報錯定位到具體的規則配置。

規則的錯誤等級分爲三級,能夠更加細粒度地控制如何應用規則:

  • "off"0 - 關閉此條規則檢查
  • "warn"1 - 警告,不會影響 exit code
  • "error"2 - 錯誤,exit code 爲 1

默認狀況下全部規則都是關閉的,"extends": "eslint:recommended" 會打開全部有「√」標記的規則,這些規則只跟着主版本更新,也能夠在 npm 中查找以 eslint-config 開頭的共享配置,經過 extends 配置項來添加。

ESLint 默認使用 Espree 做爲 JavaScript 解析器,能夠在 parser 配置項中更改解析器。解析器會將源代碼解析成抽象語法樹 AST (Abstract Syntax Tree),而後插件會根據這個 AST 來建立一些稱爲 lint rules 的斷言,來描述代碼應該是怎樣的。

評價

優勢
  • 默認規則裏面包含了 JSLint 和 JSHint 的規則,易於遷移
  • 有三種錯誤等級,能夠更細粒度地控制 lint 的行爲
  • 靈活的插件擴展機制
  • 能夠自定義規則
  • 能夠根據錯誤定位到對應的規則
  • 支持 ES6
  • 支持 JSX
缺點
  • 更大的靈活性意味着更復雜的配置
  • 比前面兩個慢

TypeScript

TSLint / typescript-eslint

用來檢查 TypeScript 的,可是2019年已經廢棄了,如今使用的是ESLint,配合 typescript-eslint 。TypeScript 團隊也宣佈將 TypeScript 代碼庫從 TSLint 遷移到 typescript-eslint

ESLint 和 TypeScript

ESLint 使用一個 parser 將 source code 轉成抽象語法樹 Abstract Syntax Tree (AST) 的數據格式,而後插件根據這個 AST 來進行 lint rules 的檢查。

TypeScript 是 JavaScript 的靜態代碼分析器,在基礎的 JavaScript 上添加了一些額外的語法。TypeScript 使用一個 parser 將 source code 轉成 AST ,而後 TypeScript Compiler 的其餘部分使用這個 AST 來執行其餘操做,例如給出類型檢查後的問題反饋等等。

然而,ESLint 和 TypeScript 使用的是不一樣格式的 AST ,這就是 typescript-eslint 這個項目存在的主要緣由。typescript-eslint 就是爲了可以一塊兒使用 ESLint 和 TypeScript 。 TSLint 使用的就是 TypeScript AST 格式,其優勢是不須要一個調和 AST 格式之間差別的工具,可是主要缺點是 TSLint 沒法重用 JavaScript 生態中圍繞 linting 已經作好的工做,而是從頭開始從新實現全部的功能,從規則到自動修復功能等等。所以,TypeScript AST 不兼容 ESLint 用戶寫成並使用的 1000 多條規則。

ESLint 解析 TypeScript:@typescript-eslint/parser

因爲TypeScript 是 JavaScript 的超集,它包含了全部 JavaScript 語法之外,還額外添加了一些語法,例如:

var x: number = 1;
複製代碼

當 TypeScript Compiler 解析這段代碼生成 TypeScript AST 時,: number 語法也會出如今語法樹中,ESLint 不借助其餘工具是沒法理解的。但 ESLint 在設計時就考慮到了這些用例。ESLint 不只僅是一個庫,而是由許多重要的移動部件組成。其中一個就是 parser 。ESLint 有一個內置的 parser 叫作 espree ,若是想支持非標準的 JavaScript 語法,只須要提供另一個 parser 給 ESLint ,它須要將 TypeScript source code 解析爲 ESLint 能夠兼容的 AST 。 @typescript-eslint/parser 就是這樣一個自定義的 ESLint parser 的實現。流程以下:

  1. ESLint 調用 ESLint config 中定義好的 parser ( @typescript-eslint/parser )。
  2. @typescript-eslint/parser 處理全部特定於 ESLint 的配置,而後調用 @typescript-eslint/typescript-estree 。這個包只用來將 TypeScript source code 轉爲一個適當的 AST 。
  3. @typescript-eslint/typescript-estree 經過調用 TypeScript Compiler 將源代碼生成一個 TypeScript AST ,而後將這個 AST 轉換爲 ESLint 須要的格式。這種 AST 格式不只僅用於 ESLint,還有更普遍的用途。它有本身的規範,也就是 ESTree@typescript-eslint/typescript-estree 還被其餘工具重用,例如 Prettier 的 TypeScript 使用。

ESLint rules 兼容 TypeScript:@typescript-eslint/eslint-plugin

TypeScript 和 ESLint 有相似的目標,所以可能出現 TypeScript 解決的一些問題本來是依賴 ESLint 解決的,二者可能會不兼容,最佳的解決方式是禁用相關的 ESLint 規則,轉而交由 TypeScript Compiler 。

因爲 TypeScript 是 JavaScript 的超集,即便 AST 進行了轉換,最終的 AST 可能還會包含一部分讓 ESLint 沒法理解的部分,因此有些 ESLint rules 可能沒法正常工做。有幾種解決方案:要麼解決 ESLint rules 的兼容性,要麼使用另外的規則,即 @typescript-eslint/eslint-plugin

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  plugins: [
    '@typescript-eslint',
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
  ],
};
複製代碼

關於 Babel 和 babel-eslint

Babel 如今支持解析 TypeScript source code 可是不進行類型檢查。這是使用 TypeScript Compiler 的一個替代方法。經過插件,它一樣也能夠支持許多其餘 TypeScript Compiler 不支持的語法。

typescript-eslint 是由 TypeScript Compiler 提供支持的。babel-eslint 支持了一些 TypeScript 自己不支持的額外的語法,可是 typescript-eslint 利用類型信息能夠支持建立 rules ,而這是 babel 作不到的,由於 babel 沒有類型檢查。由於它們是由不一樣的底層工具驅動的獨立項目,因此目前不打算將它們一塊兒使用。

其餘

stylelint

用來檢查樣式,幫助避免錯誤和強制代碼風格。能夠理解最新的 CSS 語法,從 HTML 、 markdown 及 CSS-in-JS 對象和模板中提取內聯的樣式,能夠解析類 CSS 語法,如 SCSS 、 Sass 、 Less 和 SugarSS 。支持插件,支持自定義規則。能夠自動修復大多數違反代碼風格的問題。而且是徹底可配置的,經過在根目錄添加配置文件 .stylelintrc.json 來按需配置。

commitlint

commitlint 用來檢查 commit message ,幫助團隊遵照 commit 約定,同一代碼提交風格。支持經過 npm 安裝已有的配置,或經過配置文件定義配置。使用 husky 來添加 git hooks :

// package.json
{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
複製代碼

commit-msg 鉤子會在一個新的 commit 建立時執行,經過 -E|--env 傳遞 husky 的 HUSKY_GIT_PARAMScommitlint ,並將其定向到相關的編輯文件。

代碼風格

目前主流的代碼風格主要有 Airbnb JavaScript Style GuideGoogle JavaScript Style Guide 以及 JavaScript Standard

  1. Airbnb JavaScript Style Guide (github star 數:100k):對應的 ESLint 配置:eslint-config-airbnb
  2. Google JavaScript Style Guide (25.6k):谷歌內部的 JavaScript 編碼標準。eslint-config-google
  3. JavaScript Standard (24.4k):零配置,只須要運行 standard --fix 便可自動格式化代碼,還能夠進行代碼檢查。對應的 ESLint 配置:eslint-config-standard
  4. idiomatic.js (17.1k):社區版 JavaScript 編碼風格指南。對應的 ESLint 配置:eslint-config-idiomatic

代碼格式化

Prettier

目前最主流的是 Prettier ,它是一個獨斷的代碼格式化工具,專一於 style issues 。經過解析代碼生成 AST ,而後再按照本身的規則從新輸出格式化後的代碼。Prettier 執行的時機能夠是在編輯器保存時、在 pre-commit hook 中或使用 CLI 工具在命令行中執行,以確保代碼風格的一致。

因爲歷史緣由,Prettier 仍然有一小部分選項,但官方更加推崇更少的配置項,由於其初衷就是終止團隊關於代碼風格的爭論。這些選項一部分是初期添加的,一部分是需求增大致使的,還有一部分則與兼容性有關。選項越多,越有可能在團隊內部引起爭論。

Prettier 僅僅對 style issues 起做用。Prettier fork 了 recast 項目,並在內部使用了 Philip Wadler 提出的算法,該算法考慮了最大行寬(line width),最大行寬影響了代碼的佈局和包裝代碼,因此決定了最終輸出的代碼格式,而這是其餘現有的代碼格式化工具所欠缺的。例如,即使 eslint 會給出超出設定的最大行寬的警告,也沒法自動修復。例如以下代碼:

foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne());
複製代碼

最終想要的效果多是這樣:

foo(
  reallyLongArg(),
  omgSoManyParameters(),
  IShouldRefactorThis(),
  isThereSeriouslyAnotherOne()
);
複製代碼

Wadler 算法是一個簡單的基於約束的代碼佈局系統。它「測量」代碼,若是代碼跨越了最大的行寬,就會中斷它。Prettier 禁止全部自定義樣式,方法是將源代碼解析成 AST 後,使用本身的規則同時考慮最大行寬,並在必要時包裝代碼,從新輸出格式化的代碼。

Prettier 和 linters 有何區別?

linters 有兩類 rules :

  1. Formatting rules:例如:max-len, no-mixed-spaces-and-tabs, keyword-spacing, comma-style…
  2. Code-quality rules:例如:no-unused-vars, no-extra-bind, no-implicit-globals, prefer-promise-reject-errors…

Prettier 承擔的是 Formatting rules 的工做,是一個「全自動」的風格指南。 所以,可使用 Prettier 來進行代碼風格的格式化,使用 linters 來檢查 bugs!

Prettier 和 linters 配合使用

因爲 linters 一般會包含樣式相關的規則,使用Prettier時,大多數樣式規則都是沒必要要的,並且更糟糕的是,它們可能與 Prettier 衝突!所以能夠將Prettier用於代碼格式問題,將linter用於代碼質量問題。

對於 ESLint ,能夠安裝 eslint-config-prettier ,來關閉全部不須要的或者可能會跟 Prettier 衝突的 ESLint rules。 stylelint 同理可使用 stylelint-config-prettier

最佳實踐

linter + code style + code formatter 的組合:ESLint + Airbnb + Prettier 。

具體作法:

  1. 安裝 ESLint
yarn add eslint --dev
複製代碼
  1. 初始化 ESLint 配置
yarn run eslint --init
複製代碼

配置文件:

// .eslintrc.js
module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "plugin:react/recommended",
        "airbnb",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
};
複製代碼
  1. 安裝 Prettier
yarn add --dev --exact prettier
# 若是須要配置文件(若是須要忽略文件,則建立一個 .prettierignore 文件)
echo {}> .prettierrc.json
複製代碼
  1. 設置編輯器

最好能在保存時就自動格式化代碼,讓開發者無需關注代碼格式,強迫症必備。

WebStorm 爲例:對於 2020.1 及以上的版本:Preferences | Languages & Frameworks | JavaScript | Prettier 而後啓用選項 Run on save for files

image.png

如今保存時就能夠自動格式化代碼了。

  1. 配置 ESLint

須要安裝 eslint-config-prettier ,這個包會關閉全部不須要的或可能和 Prettier 衝突的ESLint rules。

yarn add eslint-config-prettier --dev
複製代碼

而後將其添加到 ESLint 配置文件中的 extends 配置項的最後,好讓它可以覆蓋其餘配置。這裏的 prettier 除了關閉一些核心的 ESLint rules,還會關閉如下插件的部分 rules:

因爲這個配置只會關閉 rules,因此跟其餘配置一塊兒使用纔有意義,注意要放到最後:

{
    "extends": [
        "plugin:react/recommended",
        "airbnb",
        "plugin:@typescript-eslint/recommended",
        "prettier"
    ]
}
複製代碼

注意,還可使用 eslint-plugin-prettier 插件把 Prettier 當作一個 linter rule 來運行,但這不是官方推薦的作法,主要有如下幾個劣勢:

  • 編輯器中會出現不少煩人的紅色波浪線,Prettier 的哲學就是讓人忘記格式化,而不是給出一個提示。
  • 比直接運行 Prettier 要慢。
  • 它們只是一個間接層(one layer of indirection),有些東西可能會出問題。
  1. 添加 Git hooks

保險起見,最好在代碼提交前也格式化一下。也就是能夠在 pre-commit hook 裏運行 Prettier 。

執行如下命令,會依賴 package.json 的 devDependencies 中的代碼檢查工具來自動安裝並配置 huskylint-staged ,所以須要確保事先已經安裝了 Prettier 和 ESlint 。

npx mrm lint-staged
複製代碼

package.json 會自動添加相關配置(默認生成的配置針對 .js 文件的,能夠按需改成 .ts.tsx 等),這樣在每次提交前,都會經過 lint-staged 和 husky 運行 ESLint 和 Prettier 。

{
  "devDependencies": {
    "husky": ">=4",
    "lint-staged": ">=10",
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.js": "eslint --cache --fix",
    "*.{js,css,md}": "prettier --write"
  }
}
複製代碼

關於 lint-staged 的做用:在代碼提交以前 linting 纔有意義,這樣能夠確保不會將糟糕的代碼上傳到倉庫中,以及強制統一風格。可是對整個項目運行一個 lint 過程很慢,而 linting 結果多是可有可無的。因此,只須要 lint 即將要提交的文件。 lint-staged 包含一個腳本,該腳本會運行任何shell任務,將staged files 做爲參數,經過一個特定的 glob pattern 進行過濾。

husky(哈士奇)主要做用是添加 git hook(git 在特定的重要動做發生時觸發自定義腳本),用來阻止很差的 git commitgit push 等等。

參考文獻

  1. cloud.tencent.com/developer/a…
  2. www.youtube.com/watch?v=kuH…
  3. A Prettier JavaScript Formatter
相關文章
相關標籤/搜索