React Native工程中TSLint静态检查工具的探索之路
背景
建立的代碼規(guī)范沒人遵守,項目中遍地風格迥異的代碼,你會不會抓狂?
通過測試用例的程序還會出現Bug,而原因僅僅是自己犯下的低級錯誤,你會不會抓狂?
某種代碼寫法存在問題導致崩潰時,只能全工程檢查代碼,這需要人工花費大量時間Review代碼,你會不會抓狂?
以上這些問題,可以通過靜態(tài)檢查有效地緩解!
靜態(tài)檢查(Static Program Analysis)主要是以不運行程序的方式對于程序源代碼進行檢查分析的技術,而與之相反的就是動態(tài)檢查(Dynamic Program Analysis),通過實際運行程序輸入測試數據產生預期結果的技術。通過代碼靜態(tài)檢查,我們可以快速定位代碼的錯誤與缺陷,可以減少逐行閱讀代碼浪費的時間,可以(根據需要)快速掃描代碼中可能存在的漏洞等。代碼靜態(tài)檢查可以在代碼的規(guī)范性、安全性、可靠性、可維護性等方面起到重要作用。
在客戶端中,Android可以使用CheckStyle、Lint、Findbugs、PMD等工具,iOS可以使用Clang Static Analyzer、OCLint等工具。而在React Native的開發(fā)過程中,針對于JavaScript的ESLint,與TypeScript的TSLint,則成為了主要代碼靜態(tài)檢查的工具。本文將按照使用TSLint的原因、使用TSLint的方法、自定義TSLint的步驟進行探究分析。
一、使用TSLint的原因
在客戶端團隊進入React Native項目的開發(fā)過程中,面臨著如下問題:
雖然以上問題可以通過多次不斷將雷點標記出,并不斷地分享經驗與強化代碼Review過程等方式來進行緩解,但是仍面臨著React Native開發(fā)者掌握的技術水平千差萬別,知識分享傳播的速度緩慢等問題,既導致了開發(fā)成本的不斷增加和開發(fā)效率持續(xù)低下的問題,還難以避免一個坑被踩了多次的情況出現。這時急需一款可以滿足以下目標的工具:
根據上述要求的描述,靜態(tài)檢查工具TSLint可以較為有效地達成目標。
二、TSLint介紹
TSLint是硅谷企業(yè)Palantir的一個項目,它是一款可以檢查TypeScript代碼可讀性、可維護性以及功能性錯誤的靜態(tài)檢查工具,當前許多編輯器(Editors)和構建系統(tǒng)(Build Systems)支持這一工具,同時支持自定義編寫Lint規(guī)則、配置、格式化等。
當前TSLint已經包含了上百條規(guī)則,這些規(guī)則構筑了當前TSLint檢查的基礎。在代碼開發(fā)階段中,通過這些配置好的規(guī)則可以給工程一個完整的檢查,并隨時可以提示出可能存在的問題。本文內容參考了TSLint官方文檔https://palantir.github.io/tslint/。
2.1 TSLint常見規(guī)則
以下規(guī)則主要來源于TSLint規(guī)則,是某些規(guī)則的簡單介紹。
2.2 常用TSLint規(guī)則包
上述2.1所列出的規(guī)則來源于Palantir官方TSLint規(guī)則。實際還有多種,可能會用到的有以下:
我們在項目的規(guī)則配置過程中,一般采用上述規(guī)則包其中一種或者若干種同時配置,那如何配置呢?請看下文。
三、如何進行TSLint規(guī)則配置與檢查
首先,在工程package.json文件中配置TSLint包:
在根目錄中的tslint.json文件中可以根據需要配置已有規(guī)則,例如:
其中extends數組內放置繼承的TSLint規(guī)則包,上圖包括了airbnb配置的規(guī)則包、tslint-react的規(guī)則包,而rules用于配置規(guī)則的開關。
TSLint規(guī)則目前只有true和false的選項,這導致了結果要么正常,要么報錯ERROR,而不會出現WARNING等警告。
有些時候,雖然配置某些規(guī)則開啟,但是某個文件內可能會關閉某些甚至全部規(guī)則檢查,這時候可以通過規(guī)則注釋來配置,如:
/* tslint:disable */上述注釋表示本文件自此注釋所在行開始,以下的所有區(qū)域關閉TSLint規(guī)則檢查。
/* tslint:enable */上述注釋表示本文件自此注釋所在行開始,以下的所有區(qū)域開啟TSLint規(guī)則檢查。
/* tslint:disable:rule1 rule2 rule3... */上述注釋表示本文件自此注釋所在行開始,以下的所有區(qū)域關閉規(guī)則rule1 rule2 rule3…的檢查。
/* tslint:enable:rule1 rule2 rule3... */上述注釋表示本文件自此注釋所在行開始,以下的所有區(qū)域開啟規(guī)則rule1 rule2 rule3…的檢查。
// tslint:disable-next-line上述注釋表示此注釋所在行的下一行關閉TSLint規(guī)則檢查。
someCode(); // tslint:disable-line上述注釋表示此注釋所在行關閉TSLint規(guī)則檢查。
// tslint:disable-next-line:rule1 rule2 rule3...上述注釋表示此注釋所在行的下一行關閉規(guī)則rule1 rule2 rule3…的檢查檢查。
以上配置信息,這里具體參考了https://palantir.github.io/tslint/usage/rule-flags/。
3.1 本地檢查
在完成工程配置后,需要下載所需要依賴包,要在工程所在根目錄使用npm install命令完成下載依賴包。
IDE環(huán)境提示
在完成下載依賴包后,IDE環(huán)境可以根據對應配置文件進行提示,可以實時地提示出存在問題代碼的錯誤信息,以VSCode為例:
本地命令檢查
VSCode目前還有繼續(xù)完善的空間,如果部分文件未在窗口打開的情況下,可能存在其中錯誤未提示出的情況,這時候,我們可以通過本地命令進行全工程的檢查,在React Native工程的根目錄下,通過以下命令行執(zhí)行:
tslint --project tsconfig.json --config tslint.json(此命令如果不正確運行,可在之前加入./node_modules/.bin/)即為:
./node_modules/.bin/tslint --project tsconfig.json --config tslint.json從而會提示出類似以下錯誤的信息:
src/Components/test.ts[1, 7]: Class name must be in pascal case3.2 在線CI檢查
本地進行代碼檢查的過程也會存在被人遺忘的可能性,通過技術的保障,可以避免人為遺忘,作為代碼提交的標準流程,通過CI檢查后再合并代碼,可以有效避免代碼錯誤的問題。CI系統(tǒng)可以為理解為一個云端的環(huán)境,環(huán)境配置與本地一致,在這種情況下,可以生成與本地一致的報告,在美團內部可以使用基于Jenkins的Castle CI系統(tǒng), 生成結果與本地結果一致:
3.3 其他方式
代碼檢查不止局限上述階段,在代碼commit、pull request、打包等階段均可觸發(fā)。
- 代碼commit階段,通過Hook方式可以觸發(fā)代碼檢查,可以有效地將在線CI檢查階段強制提前,基本保證了在線CI檢查的完全正確性。
- 代碼pull request階段,通過在線CI檢查可以觸發(fā)代碼檢查,可以有效保證合入分支尤其是主分支的正確性。
- 代碼打包階段,通過在線CI檢查可以觸發(fā)代碼檢查,可以有效保證打包代碼的正確性。
四、自定義編寫TSLint規(guī)則
4.1 為什么要自定義TSLint規(guī)則
當前的TSLint規(guī)則雖然涵蓋了比較普遍問題的一些代碼檢查,但是實踐中還是存在一些問題的:
基于以上原因其他團隊也有自定義TSLint的先例,例如上文提到的tslint-microsoft-contrib、tslint-eslint-rules等。
4.2 自定義規(guī)則步驟
那自定義TSLint大概需要什么步驟呢,首先規(guī)則文件根據規(guī)范進行按部就班的編寫規(guī)則信息,然后根據代碼檢查邏輯對語法樹進行分析并編寫邏輯代碼,這也是自定義規(guī)則的核心部分了,最后就是自定義規(guī)則的使用了。
自定義規(guī)則的示例直接參考官方的規(guī)則是最直接的,我們能這里參考一個比較簡單的規(guī)則”class-name”。
“class-name”規(guī)則上文已經提到,它的意思是對類命名進行規(guī)范,當團隊中類相關的命名不規(guī)范,會導致項目代碼風格不統(tǒng)一甚至其他出現的問題,而”class-name”規(guī)則可以有效解決這個問題。我們可以看下具體的源碼文件:https://github.com/palantir/tslint/blob/master/src/rules/classNameRule.ts。
然后將分步對此自定義規(guī)則進行講解。
第一步,文件命名
規(guī)則命名必須是符合以下2個規(guī)則:
第二步,類命名
規(guī)則的類名是Rule,并且要繼承Lint.Rules.AbstractRule這個類型,當然也可能有繼承TypedRule這個類的時候,但是我們通過閱讀源碼發(fā)現,其實它也是繼承自Lint.Rules.AbstractRule這個類。
第三步,填寫metadata信息
metadata包含了配置參數,定義了規(guī)則的信息以及配置規(guī)則的定義。
- ruleName 是規(guī)則名,使用烤串命名法,一般是將類名轉為烤串命名格式。
- description 一個簡短的規(guī)則說明。
- descriptionDetails 詳細的規(guī)則說明。
- rationale 理論基礎。
- options 配置參數形式,如果沒有可以配置為null。
- optionExamples 參數范例 ,如沒有參數無需配置。
- typescriptOnly true/false 是否只適用于TypeScript。
- hasFix true/false 是否帶有修復方式。
- requiresTypeInfo 是否需要類型信息。
- optionsDescrition options的介紹。
- type 規(guī)則的類型。
規(guī)則類型有四種,分別為:”functionality”、”maintainability”、”style”、”typescript”。
- functionality : 針對于語句問題以及功能問題。
- maintainability:主要以代碼簡潔、可讀、可維護為目標的規(guī)則。
- style:以維護代碼風格基本統(tǒng)一的規(guī)則。
- typescript:針對于TypeScript進行提示。
第四步,定義錯誤提示信息
這個主要是在檢查出問題的時候進行提示的文字,并不局限于使用一個靜態(tài)變量的形式,但是大部分官方規(guī)則都是這么編寫,這里對此進行介紹,防止引起歧義。
第五步,實現apply方法
apply主要是進行靜態(tài)檢查的核心方法,通過返回applyWithFunction方法或者返回applyWithWalker來進行代碼檢查,其實applyWithFunction方法與applyWithWalker方法的主要區(qū)別在于applyWithWalker可以通過IWalker實現一個自定義的IWalker類,區(qū)別如下:
其中實現IWalker的抽象類AbstractWalker里面也繼承了WalkContext,
而這個WalkContext就是上面提到的applyWithFunction的內部實現類。
第六步,語法樹解析
無論是applyWithFunction方法還是applyWithWalker方法中的IWalker實現都傳入了sourceFile這個參數,這個相當于文件的根節(jié)點,然后通過ts.forEachChild方法遍歷整個語法樹節(jié)點。
這里有兩個查看AST語法樹的工具:
- AST Explorer: https://astexplorer.net/
對應源碼: https://github.com/fkling/astexplorer - TypeScript AST Viewer: https://ts-ast-viewer.com/
對應源碼: https://github.com/dsherret/ts-ast-viewer
AST Explorer
優(yōu)點:
在AST Explorer可以高亮顯示所選中代碼對應的AST語法樹信息。
缺點:
TypeScript AST Viewer
優(yōu)點:
每個版本對應對kind信息數值可能會變動,但是對應的枚舉名字是固定的,如下圖:
從而這個工具可以避免頻繁根據其數值查找對應信息。
缺點: 不能高亮顯示代碼對應的AST語法樹區(qū)域,定位效率較低。
綜上,通過同時使用上述兩個工具定位分析,可以有效地提高分析效率。
第七步,檢查規(guī)則代碼編寫
通過ts.forEachChild方法對于語法樹所有的節(jié)點進行遍歷,在遍歷的方法里可以實現自己的邏輯,其中節(jié)點的類為ts.Node:
其中kind為當前節(jié)點的類型,當然Node是所有節(jié)點的基類,它的實現還包括Statement、Expression、Declaration等,回到開頭這個”class-name”規(guī)則,我們的所有聲明類主要是class與interface關鍵字,分別對應ClassExpression、ClassDeclaration、InterfaceDeclaration, 我們可以通過上步提到的AST語法樹工具,在語法樹中看到其為一一對應的。
在規(guī)則代碼中主要通過isClassLikeDeclaration、isInterfaceDeclaration這兩個方法進行判斷的。
其中isClassLikeDeclaration、isInterfaceDeclaration對應的方法我們可以在node.js文件中找到:
判斷是對應的類型時,調用addFailureAtNode方法把錯誤信息和節(jié)點傳入,當然還可以調用addFailureAt、addFailure方法。
最終這個規(guī)則編寫結束了,有一點再次強調下,因為每個版本所對應的類型代碼可能不相同,當判斷kind的時候,一定不要直接使用各個類型對應的數字。
第八步,規(guī)則配置使用
完成規(guī)則代碼后,是ts后綴的文件,而ts規(guī)則文件實際還是要用js文件,這時候我們需要用命令將ts轉化為js文件:
tsc ./src/*.ts --outDir dist將ts規(guī)則生成到dist文件夾(這個文件夾命名用戶自定),然后在tslint.json文件中配置生成的規(guī)則文件即可。
之后在項目的根目錄里面,使用以下命令既可進行檢查:
tslint --project tsconfig.json --config tslint.json同時為了未來新增規(guī)則以及規(guī)則配置的更好的操作性,建議可以封裝到自己的規(guī)則包,以便與規(guī)則的管理與傳播。
總結
TSLint的優(yōu)點:
TSLint缺點:
使用結果及分析
在美團,有十余個頁面的單個工程首次接入TSLint后,檢查出的問題有近百條。但是由于開啟的規(guī)則不同,配置規(guī)則包的差異,檢查后的數量可能為幾十條到幾千條甚至更多。現在已開發(fā)十余條自定義規(guī)則,在單個工程內,處理優(yōu)化了數百處可能存在問題的代碼。最終TSLint接入了相關React Native開發(fā)團隊,成為了代碼提交階段的必要步驟。
通過團隊內部的驗證,文章開頭遇到的問題得到了有效地緩解,目標基本達到預期。TSLint在React Native開發(fā)過程中既保證了代碼風格的統(tǒng)一,又保證了React Native開發(fā)人員的開發(fā)質量,避免了許多低級錯誤,有效地節(jié)省了問題排查和人員溝通的成本。
同時利用自定義規(guī)則,能夠將一些兼容性問題在內的個性化問題進行總結與預防,提高了開發(fā)效率,不用花費大量時間查找問題代碼,又避免了在一個問題上跌倒多次的情況出現。對于不同經驗的開發(fā)者而言,不僅可以進行友好的提示,也可以幫助快速地定位問題,將一個人遇到的經驗教訓,用極低的成本擴散到其他團隊之中,將開發(fā)狀態(tài)從“亡羊補牢”進化到“防患未然”。
作者簡介
家正,美團點評Android高級工程師。2017 年加入美團點評,負責美團大交通的業(yè)務開發(fā)。
總結
以上是生活随笔為你收集整理的React Native工程中TSLint静态检查工具的探索之路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SegmentFault 美团云采访实录
- 下一篇: 从朴素贝叶斯到贝叶斯网