MENU 自定義 TSLint 規則實踐
TSLint Rule Develop Best Practice
TSLint 是一個非常好用的開源 TypeScript 代碼風格檢查器,它能夠在可讀性、可維護性、代碼正確性等方面為開發者提供幫助。TSLint 被廣泛用於各種前端構建工具和編輯器中。
在編寫代碼時,編譯器會根據 TSLint 拋出高亮提示,在代碼編譯時,編譯工具可以運行 TSLint 然後及時拋出錯誤阻斷編譯的繼續,防止不符合規範的代碼進入生產環境。 TSLint本身擁有非常豐富的規則庫,見 TSLint core rules?。
背景
我目前正在做一個中文前端項目的國際化,原代碼中存在大量的中文字元串,為了能夠統一管理和翻譯界面文字,同時防止新增代碼中出現中文字元串,我希望能夠有一個 TSLint 規則幫我檢查代碼中的中文字元,但是已有的規則並不支持。
TSLint 自稱擁有很強的定製能力——『千萬不要相信官方宣傳』——在我閱讀了它的文檔之後終於懂了這句話。我沒有在 google 上找到關於自定義 TSLint 規則的 best practice,看來只能自己根據文檔看源碼了。好在 TSLint 的源碼使用 TypeScript 編寫,在類型聲明的幫助下我很快弄懂了自己需要什麼並完成了開發 。
TSLint 工程擁有完整的規則開發框架。本文將從規則的實現、如何進行代碼檢查、如何測試規則的正確性等三方面介紹如何借用 TSLint 工程開發你自己的規則。希望讀者在閱讀完本文後可以輕鬆地定製自己的規則,避免陷入我所陷入過得迷茫境地。
本文假定讀者使用過 TSLint,至少已經了解 tslint.json 的用法。
規則定義
TSLint 使用 TypeScript 編寫,規則用 Rule 表示。每個 Rule 都繼承自 Lint.Rule.AbstractRule 。 AbstractRule 最核心的方法是 apply 。在 apply 方法中返回 Lint.RuleFailure 類型的檢查結果,外部插件結合這些結果便可以做可視化/命令行錯誤提示。一個 Rule 的實現一般如下:
其實 TSLint 的奧義在 this.applyWithWalker 或 this.applyWithFunction ,這兩個函數返回了代碼中的錯誤。這兩個方法的入參都有 walker 對象,只是在實現上有些微區別,大部分的規則都選用 this.applyWithFunction (我猜測選擇哪一種只是個人喜好) 。讓我們看一下他們的定義與實現。
applyWithWalker
applyWithFunction
乍一看後者比前者複雜很多,但如果你仔細看,其實他們的實現不超過三行。這兩個函數的區別在對 walker 的定義上。
applyWithWalker 接受一個參數,是類型為 IWalker 的對象。在運行時,首先通過IWalker.walk 方法向 walker 傳入被檢查文件的抽象語法樹根節點 SourceFile 類型對象,最後通過調用 walker.getFailures 方法獲得類型為 RuleFailure[] 的錯誤信息並返回。
applyWithFunction 接受2到4個參數,其中第一個參數是被檢查文件的抽象語法樹根節點 SourceFile 類型對象,第二個參數是 (ctx: WalkContext, programOrChecker?: U) => void 類型的 walk 函數。在運行時,首先生成包含文件信息和規則信息的 WalkContext 類型的 ctx 變數,接著 ctx 傳入 walk 函數。walk 函數遇到語法錯誤時會將錯誤存入類型為 RuleFailure[] 的 ctx.failures 對象中。最終將 ctx.failures 返回。
代碼檢查
規則的作用就是在規定時候拋出錯誤,幫助我們更好地編寫程序。上文中講了一個規則的實現是怎樣的,但是讀者們是不是仍然對規則的運作原理一頭霧水?其實大家只需要弄明白兩個問題:
我該怎麼讀取被檢查的代碼?
2. 我該如何拋出錯誤?
AST(抽象語法樹)
我該怎麼讀取被檢查的代碼?這個也曾困擾過我,因為我不知道 TSLint 是如何和代碼文件『發生關係』的。
TSLint 每次的檢查以文件為單位,還記得上文中拗口的 _被檢查文件的抽象語法樹根節點 _ SourceFile 類型 么?它就是規則和被檢查文件的橋樑,它包含了被檢查文件的所有信息。AST(抽象語法樹)是 代碼文件 的結構化表示,有了它我們就可以對原文件做語法檢查或代碼分析。TSLint 用的是 TypeScript AST,語法樹的每個節點對應原文件的一小段文字,並包含了一些額外信息。TSLint 中的語法樹節點有以下屬性:
Node 上還有一些有用的方法,比如 getFullText 和 getText 可以獲得節點的字元串(我沒有看明白這兩者的區別,如果有人知道請告訴我)。
Rule用到的 walker 正是通過從這棵語法樹 遍歷 代碼文件的。
上面是一種簡單的 walker 實現。使用 ts.forEachChild 方法遍歷 Node 的子節點。
TypeScript AST Viewer ,在左側輸入 TypeScript 代碼段,右側實時預覽語法樹。
拋出 TSLint 錯誤
當我們知道如何遍歷原文件,那麼最後一個問題就是『我們該如何找出錯誤並拋出錯誤』了。
代碼檢查的規則由需求決定。檢查代碼時依靠文件的 AST,其實就是對單個 Node 或多個 Node 之間的關係進行分析。 Node 包含很多屬性和方法,下面以 Node.kind 為例說明。
Node.kind
Node.kind 是一個表示這個節點語義類型的整數,枚舉類 ts.SyntaxKind 有完整的對應關係。一個節點可能有0個或以上子節點,子節點也有 kind 屬性,所以不同 SyntaxKind 之間可能存在包含關係。一般這種關係是單向的。
我在實現中文字元檢查時,只使用了 Node.kind 屬性和 Node.getFullText() 方法。首先找到所有可能出現中文字元的 SyntaxKind 類型,選出所有符合條件的節點 Node ,再對節點包含的文字做正則匹配即可。
最後,我只需要將有問題的節點拋給外部就可以了。如何拋出錯誤?如果你使用的是 applyWithWalker 你需要把錯誤 push 到 walker.failures。一般的做法是通過繼承輔助類 Lint.AbstractWalker 實現 walker,然後在出錯的地方調用 Lint.AbstractWalker 的 addFailure 方法。
如果你使用的是 applyWithFunction 你只需要在 Walker 方法中調用:
規則元數據
可能你沒有注意到 Rule 類的實現上有一個 public 的靜態屬性叫做 metadata 。 metadata(元數據)規定了規則和外部交互的信息。以下為 no-null-keyword 規則的元數據 :
規則名稱
name 非常重要,它是這個規則對外的名稱,使用者靠這個字元串辨別不同規則。關於 name 有一點要說的是,TSLint 規定每個規則的入口文件文件名為該規則名的駝峰式命名法加上 Rule 尾綴。比如 no-null-keyword 的文件名就叫 noNullKeywordRule.ts,位於 TSLint 工程的 src/ruls/ 路徑下。
規則類型
規則元數據有一個 type 欄位,它表示這個規則的分類。所有的 TSLint 規則分為四大類:
1. TypeScript-specific:僅對 Typescript 特性的提示。如:no-empty-interface。
2. Functionality:適用於 js 的,在編碼或特容易出錯代碼上做的提示。如:no-null-key。
3. Maintainability:使工程維護更加簡單的規則。如:trailing-comma。
4. Style:代碼樣式規則。如:align。
正確性測試
你寫完了一條規則,卻無處測試?TSLint 自帶測試規則測試框架。它的測試以每條規則為單元,一條規則可以有一個或多個測試文件。需要在 TSLint 工程根目錄運行以下腳本。
這條命令把用 TypeScript 編寫的規則文件編譯成 js 版本,供 node 環境調用。
這條命令會對所有規則進行測試。
小tip:在開發時改動 ruleTestRunner.js 腳本中的 testDirectories 遍歷邏輯,使其只執行你的規則。
對了,最後還要說一下如何編寫測試用例,此處可以借鑒 官方文檔 。測試用例位於 test/rules/ 路徑下,每個規則對應的測試用例都放在以該規則名命名的文件夾下。最簡單的測試用例包含兩個文件: tslint.json 和 test.ts.lint 。前者用來配置該規則,後者用來編寫一些可能遇到的出錯環境,並手寫錯誤提示效果。運行 test:rules 腳本,當規則生成的錯誤提示和你手寫的錯誤提示完全匹配時,就通過了測試。為了保證正確性,要儘可能地寫出全面的測試用例哦!~
架構師視頻資料分享鏈接:
data:text/html;charset=UTF-8;base64,
5p625p6E5biI5a2m5Lmg5Lqk5rWB576k5Y+35pivNTc1NzUxODU0Cg==
複製粘貼在網站即可!
※Spring Boot與Kotlin 使用MongoDB資料庫
TAG:java技術交流 |