
如果您是現今的 JavaScript 開發者,您很有可能正在使用 ESLint 和 TypeScript 的組合來輔助開發。這些工具執行相似但不同的功能。ESLint 是一個 linter,而 TypeScript 是一個 類型檢查器。
Linters 和類型檢查器是兩種 靜態分析 工具,它們分析程式碼並報告偵測到的問題。雖然它們乍看之下可能很相似,但 linters 和類型檢查器偵測到不同類別的問題,並且在不同的方面很有用。
為了理解這些差異,首先了解什麼是靜態分析以及它為何有用會很有幫助。
什麼是靜態分析?
靜態分析是在不執行程式碼的情況下檢查原始碼。這與 動態分析 不同,後者是在執行原始碼時檢查原始碼。因此,動態分析帶來了執行惡意程式碼或產生副作用的固有危險,而靜態分析無論原始碼為何,執行起來都是安全的。
靜態分析對於提高程式碼的可讀性、可靠性和整體品質非常有幫助。許多開發者依靠靜態分析來強制執行一致的程式碼格式和風格、確保程式碼有良好的文件記錄,以及捕捉可能的錯誤。由於靜態分析在原始碼上執行,因此它可以在編寫程式碼時在編輯器中提出改進建議。
ESLint 的靜態分析組織為一系列個別配置的 lint 規則。由於沒有兩個規則會相互作用,因此您可以根據自己的偏好安全地開啟和關閉每個規則。雖然 TypeScript 有一些個別配置的選項,但大部分分析是在類型檢查功能中執行的。
ESLint 和 TypeScript 使用一些相同的分析形式來偵測程式碼中的缺陷。它們都分析程式碼中範圍和變數的建立和使用方式,並且可以捕捉到諸如引用不存在的變數等問題。我們將探討兩者使用程式碼分析資訊的不同方式。
深入探討 linting 與類型檢查
Linters 主要報告可能的缺陷,也可以用於強制執行主觀意見。ESLint 和其他 linters 捕捉到的問題可能類型安全,也可能不是類型安全,但都是潛在的錯誤來源。許多開發者依靠 linters 來確保他們的程式碼遵循框架和語言的最佳實踐。
例如,開發者有時會在 switch
語句的 case
結尾遺漏 break
或 return
。這樣做是類型安全的,並且 JavaScript 和 TypeScript 都允許。但在實務上,這幾乎總是一個錯誤,會導致下一個 case
語句意外執行。ESLint 的 no-fallthrough
可以捕捉到這個可能的錯誤
function logFruit(value: "apple" | "banana" | "cherry") {
switch (value) {
case "apple":
console.log("🍏");
break;
case "banana":
console.log("🍌");
// eslint(no-fallthrough):
// Expected a 'break' statement before 'case'.
case "cherry":
console.log("🍒");
break;
}
}
// Logs:
// 🍌
// 🍒
logFruit("banana");
另一方面,類型檢查器確保值僅以值類型允許的方式使用。編譯語言(如 Java)在編譯階段執行類型檢查。由於 JavaScript 無法指示綁定的預期類型,因此它無法自行執行類型檢查。這就是 TypeScript 的用武之地。
透過允許顯式類型註釋(以及一些類型的隱式偵測),TypeScript 將類型資訊覆蓋在 JavaScript 程式碼之上,以執行類似於編譯語言中的類型檢查。例如,TypeScript 會在以下 logUppercase(9001)
呼叫中報告類型錯誤,因為 logUppercase
被宣告為接收 string
而不是 number
function logUppercase(text: string) {
console.log(text.toUpperCase());
}
logUppercase(9001);
// ~~~~
// Argument of type 'number' is not assignable to parameter of type 'string'.
TypeScript 專注於報告已知的錯誤,而不是潛在的問題;TypeScript 報告的錯誤沒有任何主觀性,也沒有辦法實作專案特定的偏好設定。
看待 ESLint 和 TypeScript 之間差異的另一種方式是,TypeScript 強制執行您可以做什麼,而 ESLint 強制執行您應該做什麼。
細緻的擴充性
ESLint 和 TypeScript 之間的另一個差異在於配置的細緻程度。
ESLint 運行一組可個別配置的 lint 規則。如果您不喜歡特定的 lint 規則,您可以為一行程式碼、一組檔案或整個專案關閉它。ESLint 也可以透過 外掛程式 擴充,這些外掛程式新增了新的 lint 規則。外掛程式特定的 lint 規則擴展了 ESLint 配置可以選擇的程式碼檢查範圍。
例如,此 ESLint 配置啟用了來自 eslint-plugin-jsx-a11y
的建議規則,這是一個外掛程式,為使用 JSX 函式庫(如 Solid.js 和 React)的專案新增了可訪問性檢查
import js from "@eslint/js";
import jsxA11y from "eslint-plugin-jsx-a11y"
export default [
js.configs.recommended,
jsxA11y.flatConfigs.recommended,
// ...
];
使用 JSX 可訪問性規則的專案隨後會被告知其程式碼是否違反了常見的可訪問性指南。例如,渲染沒有描述文字的原生 <img>
標籤會收到來自 jsx-a11y/alt-text
的報告
const MyComponent = () => <img src="source.webp" />;
// ~~~~~~~~~~~~~~~~~~~~~~~~~
// eslint(jsx-a11y/alt-text):
// img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.
透過新增來自外掛程式的規則,ESLint 配置可以針對專案建構所使用的框架的特定最佳實踐和常見問題進行客製化。
另一方面,TypeScript 是透過專案層級的一組編譯器選項來配置的。tsconfig.json
檔案 允許您設定編譯器選項,這些選項會變更專案中所有檔案的類型檢查。這些編譯器選項是為 TypeScript 全域設定的,並且通常會變更大範圍的類型檢查行為。TypeScript 不允許單一專案中不同檔案的編譯器選項有所不同。
重疊領域
雖然 ESLint 和 TypeScript 的運作方式不同,並且專門針對不同領域的程式碼缺陷,但仍有一些重疊之處。特定類型的程式碼缺陷介於「最佳實踐」和「類型安全」之間,因此可以被這兩種工具捕捉到。
我們建議在您的 TypeScript 專案中同時使用 ESLint 和 TypeScript,以確保您捕捉到最廣泛的數量和類型的缺陷。以下是一些入門步驟
- 在您的 ESLint 配置檔案中,使用
- ESLint
js.configs.recommended
配置 - 來自 typescript-eslint 的
tseslint.configs.recommended
配置 - 任何與您的專案函式庫和框架相關的社群外掛程式
- ESLint
- 在您的 TypeScript 配置檔案中,啟用
strict
模式 以捕捉盡可能多的類型安全問題
注意: typescript-eslint 的 tseslint.configs.recommended
會停用核心 ESLint 規則,這些規則對於 TypeScript 沒有幫助。此配置會保留任何與類型檢查一起使用的核心 ESLint 規則。
未使用的區域變數和參數
我們建議在使用 linting 時保持關閉的唯一 TypeScript 編譯器選項是那些啟用檢查未使用變數的選項
noUnusedLocals
- 報告未使用的宣告區域變數noUnusedParameters
- 報告未使用的函式參數
當不使用 ESLint 時,這些編譯器選項很有用。但是,它們不像 lint 規則那樣可配置,因此無法根據專案的偏好設定配置為更高或更低的嚴格程度。例如,編譯器選項被硬式編碼為始終忽略名稱以 _
開頭的任何變數,而 ESLint 的 no-unused-vars
在配置為這樣做之前,不會以任何不同的方式對待這些變數。
例如,以下 registerCallback
函式為其回呼宣告了兩個參數 id
和 message
,但使用它的開發者只需要 message
。TypeScript 的 noUnusedParameters
編譯器選項不會標記未使用的參數 _
type Callback = (id: string, message: string) => void;
declare function registerCallback(callback: Callback): void;
// We only want to log message, not id
registerCallback((_, message) => console.log(message));
JavaScript 中未使用的變數也可以被 ESLint 的 no-unused-vars
規則捕捉到;在 TypeScript 程式碼中,最好使用 @typescript-eslint/no-unused-vars
。lint 規則可以配置為忽略名稱以 _
開頭的變數。
此外,lint 規則預設會忽略任何本身被使用的參數之前的所有參數。有些專案偏好永遠不允許未使用的參數,無論名稱或位置為何。這些更嚴格的偏好設定有助於防止 API 設計導致開發者建立許多未使用的參數。
更嚴格的 ESLint 配置將能夠報告 _
參數
/* eslint @typescript-eslint/no-unused-vars: ["error", { "args": "all", "argsIgnorePattern": "" }] */
type Callback = (id: string, message: string) => void;
declare function registerCallback(callback: Callback): void;
// We only want to log message, not id
registerCallback((_, message) => console.log(message));
// ~
// eslint(@typescript-eslint/no-unused-vars):
// '_' is declared but never used.
no-unused-vars
規則提供的額外可配置性使其能夠充當與其對應的 TypeScript 編譯器選項更細緻可配置的版本。
💡 請參閱
no-unused-binary-expressions
:從程式碼審查的細微之處到生態系統的改進,以了解 linting 和類型檢查之間部分重疊的更多程式碼檢查領域。
ESLint 對於 TypeScript 有用嗎?
是的。
如果您正在使用 TypeScript,那麼使用 ESLint 仍然非常有用。事實上,ESLint 和 TypeScript 在一起使用時功能最強大。
具有類型資訊的 ESLint
傳統的 ESLint 規則一次在單個檔案上運行,並且不了解專案中的其他檔案。它們無法根據其他檔案的內容對檔案做出決策。
但是,如果您的專案設定為使用 TypeScript,您可以選擇加入「類型檢查」lint 規則:可以使用類型資訊的規則。這樣做,類型檢查的 lint 規則可以根據其他檔案做出決策。
例如,@typescript-eslint/no-for-in-array
能夠偵測到對陣列類型的值的 for...in
迴圈,即使這些值來自其他檔案。TypeScript 不會報告對陣列的 for...in
迴圈的類型錯誤,因為這樣做在技術上是類型安全的,並且可能是開發者想要的。但是,可以將 linter 配置為注意到開發者可能犯了錯誤,並且本意是使用 for...of
迴圈
// declare function getArrayOfNames(): string[];
import { getArrayOfNames } from "./my-names";
for (const name in getArrayOfNames()) {
// eslint(@typescript-eslint/no-for-in-array):
// For-in loops over arrays skips holes, returns indices as strings,
// and may visit the prototype chain or other enumerable properties.
// Use a more robust iteration method such as for-of or array.forEach instead.
console.log(name);
}
類型化的 linting 以降低 linting 速度為代價,使其大致與類型檢查的速度相當,但提供了一組更強大的 lint 規則。請參閱 類型化的 Linting:有史以來最強大的 TypeScript Linting,以了解有關使用 typescript-eslint 進行類型化 linting 的更多詳細資訊。
使用 linting 的 TypeScript
TypeScript 為 JavaScript 增加了額外的複雜性。這種複雜性通常是值得的,但任何增加的複雜性都帶來了誤用的可能性。ESLint 有助於阻止開發者在程式碼中犯下特定於 TypeScript 的錯誤。
例如,TypeScript 的 {}
(「空物件」)類型經常被剛接觸 TypeScript 的開發者誤用。它在視覺上看起來應該是指任何 object
,但實際上是指任何非 null
、非 undefined
值 — 包括原始類型,例如 number
和 string
。@typescript-eslint/no-empty-object-type
捕捉到 {}
類型的用法,這些用法可能原本是指 object
或 unknown
export function logObjectEntries(value: {}) {
// ~~
// eslint(@typescript-eslint/no-empty-object-type):
// The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`.
// - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.
// - If you want a type meaning "any object", you probably want `object` instead.
// - If you want a type meaning "any value", you probably want `unknown` instead.
console.log(Object.entries(value));
}
logObjectEntries(0); // No type error!
使用 ESLint 強制執行特定於語言的最佳實踐有助於開發者學習和正確使用 TypeScript。
結論
Linters(如 ESLint)和類型檢查器(如 TypeScript)對於開發者來說都是寶貴的資產。兩者捕捉到不同領域的程式碼缺陷,並且在可配置性和可擴展性方面具有不同的理念。
- ESLint 檢查程式碼是否符合最佳實踐並保持一致性,強制執行您應該編寫的內容。
- TypeScripts 檢查程式碼是否「類型安全」,強制執行您可以編寫的內容。
總而言之,這兩個工具可幫助專案編寫錯誤更少且更一致的程式碼。我們建議任何使用 TypeScript 的專案都額外使用 ESLint。