新聞中心
一、背景
Flutter雖然火了很久,但是大家對(duì)Flutter代碼靜態(tài)檢查原理與應(yīng)用依然有很多大大小小的問(wèn)題,在Flutter開(kāi)發(fā)中就存在一些大家都會(huì)遇到普適性的問(wèn)題:

創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括夷陵網(wǎng)站建設(shè)、夷陵網(wǎng)站制作、夷陵網(wǎng)頁(yè)制作以及夷陵網(wǎng)絡(luò)營(yíng)銷(xiāo)策劃等。多年來(lái),我們專(zhuān)注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,夷陵網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶(hù)以成都為中心已經(jīng)輻射到夷陵省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶(hù)的支持與信任!
- 團(tuán)隊(duì)沉淀了很多flutter編碼規(guī)范。目前團(tuán)隊(duì)完全靠人工CR,人工CR存在效率低,容易遺漏。
- 另外一方面,我們?cè)跇I(yè)務(wù)迭代中也總結(jié)了大量代碼質(zhì)量、代碼穩(wěn)定性、代碼性能方面的最佳實(shí)踐。同樣這些最佳實(shí)踐也是通過(guò)人工CR來(lái)保證的。
上述兩個(gè)點(diǎn),均指向了人工CR的缺陷與不足,因此我們急需一些自動(dòng)化手段來(lái)解決人工CR的效率低、容易遺漏這一問(wèn)題。
所以想通過(guò)本文來(lái)為大家介紹下,代碼靜態(tài)分析可以在編碼時(shí)讓IDE實(shí)時(shí)提示程序員其代碼存在缺陷甚至根據(jù)最佳實(shí)踐的內(nèi)容提示更好的代碼實(shí)現(xiàn)。
二、代碼靜態(tài)分析
IDE與代碼分析服務(wù)器
IDE如何提示代碼存在問(wèn)題
1. 當(dāng)打開(kāi)android studio編輯器時(shí),首先會(huì)初始化AnalyzerServer服務(wù)。
2. AnalyzerServer通過(guò)創(chuàng)建Isolate啟動(dòng)加載AnalyzerPlugin插件main方法。
3. AnalyzerPlugin會(huì)一直處在循環(huán)當(dāng)中,等待調(diào)用。
4. 當(dāng)修改了代碼,IDE觸發(fā)文件改動(dòng)通知到AnalyzerServer,AnalyzerServer通知文件變動(dòng)到AnalyzerPlugin。觸發(fā)analyzer代碼靜態(tài)分析方法。
analyzer_server作用是什么?扮演著什么角色?
代碼分析服務(wù)器
analyzer_server主要提供Dart代碼的分析和檢查功能。同時(shí),也是Dart語(yǔ)言服務(wù)器協(xié)議(LSP)的實(shí)現(xiàn),可以通過(guò)LSP協(xié)議與IDE進(jìn)行通信,并提供相關(guān)的API和功能。
IDE與analyzer_server的關(guān)系:
1. 打開(kāi)Dart文件:IDE可以通過(guò)LSP協(xié)議發(fā)送打開(kāi)Dart文件的請(qǐng)求,analyzer_server會(huì)加載Dart文件,并進(jìn)行代碼分析和檢查。
2. 獲取Dart文件的分析結(jié)果:IDE可以通過(guò)LSP協(xié)議發(fā)送獲取Dart文件分析結(jié)果的請(qǐng)求,analyzer_server會(huì)返回分析結(jié)果,例如Dart文件中的變量、函數(shù)、類(lèi)等信息。
3. 執(zhí)行Dart代碼:IDE可以通過(guò)LSP協(xié)議發(fā)送執(zhí)行Dart代碼的請(qǐng)求,analyzer_server會(huì)加載并執(zhí)行Dart代碼,并返回執(zhí)行結(jié)果。
4. 擴(kuò)展Dart分析器功能:IDE可以通過(guò)LSP協(xié)議調(diào)用analyzer_plugin插件提供的API,擴(kuò)展Dart分析器的功能。例如,IDE可以通過(guò)analyzer_plugin插件來(lái)實(shí)現(xiàn)自定義的代碼檢查、代碼重構(gòu)等功能。
analyzer_server 和 analyzer_plugin 的關(guān)系:
- analyzer_server 可以加載和運(yùn)行 analyzer_plugin 來(lái)提供額外的分析功能。
- analyzer_plugin 是一個(gè)用于擴(kuò)展 analyzer_server 功能的插件,它可以實(shí)現(xiàn)自定義的 lint 規(guī)則、代碼生成、代碼補(bǔ)全等功能。
- analyzer_server 負(fù)責(zé)啟動(dòng)、停止和管理 analyzer_plugin 的生命周期。
從上面可以看出,analyzer_server負(fù)責(zé)與IDE進(jìn)行通信,同時(shí)也會(huì)加載analyzer_plugin插件,實(shí)現(xiàn)開(kāi)發(fā)者可以自定義規(guī)則。
自定義代碼分析插件工程搭建及原理
插件入口環(huán)境配置
1. 新建flutter插件 dw_pink_lint_rules。
2. 在插件目錄下新建/tools/analyzer_plugin/pubspec.yaml文件,依賴(lài)dw_pink_lint_rules。
圖片
3. 再新建/tools/analyzer_plugin/bin/plugin.dart,main()是插件啟動(dòng)的入口,IDE啟動(dòng)或者點(diǎn)擊重啟按鈕時(shí),analyzer_server會(huì)調(diào)用到入口啟動(dòng)插件。
圖片
start()方法啟動(dòng)一個(gè)繼承自ServerPlugin的自定義類(lèi)DwServerPlugin,所有自定義的工作實(shí)現(xiàn)在這里完成。
4. 目錄結(jié)構(gòu)如下:
圖片
圖片
主工程中使用自定義插件
開(kāi)發(fā)者可以通過(guò)插件機(jī)制,來(lái)擴(kuò)展其自定義的代碼分析、代碼補(bǔ)全等功能。那如何自定義一個(gè)代碼分析插件?
1. 在主工程pubspec.yaml中引入dw_pink_lint_rules依賴(lài)。
2. 同時(shí)在analysis_options.yaml配置插件入口( analyzer_server 會(huì)讀取解析這個(gè)yaml配置文件,找到自定義的插件,也就是dw_pink_lint_rules)。
圖片
插件啟動(dòng)到自定義代碼入口
圖片
1. 在/tools/bin/plugin.dart中main()是插件入口,這里的入口就是通過(guò)analyzer_server調(diào)用啟動(dòng)。
圖片
2. 調(diào)用lib/starter.dart中的start(),這里初始化ServerPluginStarter()對(duì)象及DwServerPlugin()對(duì)象
圖片
DwServerPlugin是自定義的實(shí)現(xiàn)類(lèi),繼承自ServerPlugin。這個(gè)類(lèi)主要是用于創(chuàng)建分析驅(qū)動(dòng)器、執(zhí)行代碼靜態(tài)分析、發(fā)送分析結(jié)果給analyzer_server,并處理analyzer_server發(fā)送的分析請(qǐng)求。同時(shí)實(shí)現(xiàn)了一些自定義的方法來(lái)實(shí)現(xiàn)特定的功能。
3. ServerPluginStarter調(diào)用的是Driver()初始化并調(diào)用start()
圖片
ServerPluginStarter實(shí)際構(gòu)造對(duì)象是Driver,這里新建了一個(gè)PluginIsolateChannel(),用于與analyzer_server進(jìn)行通訊。
每個(gè)插件運(yùn)行在一個(gè)獨(dú)立的Isolate中,這使得它們可以在不阻塞主線(xiàn)程的情況下執(zhí)行耗時(shí)任務(wù)。為了使不同的Isolate之間可以進(jìn)行通信,F(xiàn)lutter提供了IsolateChannel的API。插件使用IsolateChannel來(lái)與主Isolate中的analysis_server進(jìn)行通信,以序列化和傳遞數(shù)據(jù)。analysis_server在接收到請(qǐng)求后會(huì)在自己的Isolate中執(zhí)行相應(yīng)的任務(wù),并將結(jié)果通過(guò)IsolateChannel返回給插件所在的Isolate。這種通訊方式使得插件可以在不同的Isolate之間傳輸數(shù)據(jù),而不會(huì)阻塞主線(xiàn)程。
4. DwServerPlugin會(huì)調(diào)用了 analysisDriverScheduler 的 start 方法,開(kāi)始調(diào)度分析驅(qū)動(dòng)器。
5. AnalysisDriverScheduler 類(lèi)是一個(gè)用于管理多個(gè)分析驅(qū)動(dòng)器的調(diào)度器,它負(fù)責(zé)為分析驅(qū)動(dòng)器提供任務(wù)隊(duì)列、任務(wù)執(zhí)行器和回調(diào)接口,并根據(jù)驅(qū)動(dòng)器的優(yōu)先級(jí)和依賴(lài)關(guān)系,安排驅(qū)動(dòng)器的執(zhí)行順序,從而實(shí)現(xiàn)高效、可靠的代碼分析。
6. channel 主要作用有兩個(gè):
- 監(jiān)聽(tīng)服務(wù)端發(fā)送的消息,并進(jìn)行處理。
- 可用于插件主動(dòng)發(fā)送消息,如收集到的error消息。
在了解完插件的啟動(dòng)流程后,我們可以看看自定義插件應(yīng)該怎么實(shí)現(xiàn)?
7. 實(shí)現(xiàn)自定義DwServerPlugin類(lèi)
下面代碼實(shí)現(xiàn)了 ServerPlugin 類(lèi)中的 createAnalysisDriver 方法,其主要作用是創(chuàng)建一個(gè) Dart 語(yǔ)言的分析驅(qū)動(dòng)器,并注冊(cè)一個(gè)回調(diào)函數(shù)來(lái)處理分析結(jié)果。
圖片
具體的實(shí)現(xiàn)步驟如下:
1. 指定分析根目錄與過(guò)濾白名單文件夾
2. 創(chuàng)建AnalysisDriver driver ,啟動(dòng)監(jiān)聽(tīng)邏輯
3. 監(jiān)聽(tīng)到變化時(shí),執(zhí)行l(wèi)inter代碼分析邏輯
執(zhí)行校驗(yàn)Linter邏輯
圖片
這段代碼分析邏輯,會(huì)創(chuàng)建一個(gè)DwChecker類(lèi),通過(guò)AST遍歷訪(fǎng)問(wèn)節(jié)點(diǎn),對(duì)代碼做靜態(tài)分析。
通過(guò)訪(fǎng)問(wèn)AST(抽象語(yǔ)法樹(shù))做代碼靜態(tài)分析
要遍歷 Dart 代碼的抽象語(yǔ)法樹(shù),可以使用 ast 包中的訪(fǎng)問(wèn)者模式。accept方法是AST節(jié)點(diǎn)的一個(gè)方法,用于接受訪(fǎng)問(wèn)者(visitor)。在Dart中,AST節(jié)點(diǎn)是由Dart解析器生成的,它們代表了源代碼中的語(yǔ)法元素,例如函數(shù)、類(lèi)、變量等等。訪(fǎng)問(wèn)者(visitor)是一個(gè)實(shí)現(xiàn)了訪(fǎng)問(wèn)AST節(jié)點(diǎn)的接口的類(lèi),它可以對(duì)AST節(jié)點(diǎn)進(jìn)行遍歷,并根據(jù)需要執(zhí)行相應(yīng)的操作。
當(dāng)我們調(diào)用一個(gè)AST節(jié)點(diǎn)的accept方法時(shí),它會(huì)調(diào)用訪(fǎng)問(wèn)者的相應(yīng)方法(例如visitPostfixExpression等等),并將自己作為參數(shù)傳遞給訪(fǎng)問(wèn)者。訪(fǎng)問(wèn)者可以使用這個(gè)AST節(jié)點(diǎn)來(lái)獲取有關(guān)該節(jié)點(diǎn)的信息,并根據(jù)需要執(zhí)行相應(yīng)的操作。
在實(shí)際使用中,我們通常會(huì)創(chuàng)建一個(gè)訪(fǎng)問(wèn)者類(lèi),繼承自AstVisitor或者RecursiveAstVisitor類(lèi),并實(shí)現(xiàn)其中的方法。然后,我們可以創(chuàng)建一個(gè)AST節(jié)點(diǎn)對(duì)象,并調(diào)用其accept方法,將訪(fǎng)問(wèn)者對(duì)象傳遞給該方法。這樣,就會(huì)觸發(fā)對(duì)AST節(jié)點(diǎn)的遍歷,并調(diào)用訪(fǎng)問(wèn)者的相應(yīng)方法。
具體來(lái)說(shuō),以下是使用訪(fǎng)問(wèn)者模式遍歷 AST 的步驟:
1. 定義一個(gè)繼承自 RecursiveAstVisitor 的訪(fǎng)問(wèn)者類(lèi),并實(shí)現(xiàn)相應(yīng)的 visit 方法。
圖片
2. 創(chuàng)建一個(gè)訪(fǎng)問(wèn)者對(duì)象,并使用 unit 對(duì)象的 accept 方法遍歷 AST。
圖片
通過(guò)AST遍歷的方式可以訪(fǎng)問(wèn)的指定的token。有了這些基礎(chǔ)知識(shí),下面可以開(kāi)始實(shí)現(xiàn)代碼分析的自定義部分邏輯。
自定義代碼分析插件實(shí)現(xiàn)
下面將列舉三個(gè)由易到難自定義規(guī)則,讓讀者更好的了解實(shí)現(xiàn)一個(gè)自定義規(guī)則是如何實(shí)現(xiàn)的,在實(shí)際實(shí)現(xiàn)過(guò)程中會(huì)遇到哪些挑戰(zhàn)?
規(guī)則一:context.read()不能在await之后使用
context.read()在await之后使用,在頁(yè)面退出或其他場(chǎng)景之后會(huì)拋異常,使用代碼靜態(tài)分析能很好的解決此類(lèi)異常問(wèn)題。
實(shí)現(xiàn)
在當(dāng)前節(jié)點(diǎn)context.read()向前查找是否有await,有則報(bào)錯(cuò)。
1. 分析context.read() 屬于方法調(diào)用;從AST遍歷訪(fǎng)問(wèn)可知在visitMethodInvocation()中,方法調(diào)用是read且context是buildContext,則去查找。
2. 能定位到context.read的token,接下來(lái)需要做的是遍歷向前查找是否有await。
圖片
這段代碼做了一件事: 向前去查找是否有await語(yǔ)句。
具體來(lái)說(shuō),在查找的過(guò)程中,會(huì)執(zhí)行以下操作:
1. 如果當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)是一個(gè)Block對(duì)象或者SwitchCase對(duì)象,則調(diào)用checkStatements函數(shù),檢查其中的每個(gè)語(yǔ)句是否有await操作。
2. 如果當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)不屬于上述任何一種類(lèi)型,則將當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)作為新的child節(jié)點(diǎn),并繼續(xù)向上遍歷。
如果找到了使用await異步操作,就會(huì)調(diào)用addError函數(shù),將相應(yīng)的錯(cuò)誤信息添加到visitor對(duì)象中。如果遍歷完整個(gè)AST節(jié)點(diǎn)樹(shù),仍然沒(méi)有找到await,則函數(shù)會(huì)正常返回,不執(zhí)行任何操作。
如何判斷是否有await
具體來(lái)說(shuō),首先創(chuàng)建了一個(gè)_AwaitVisitor對(duì)象visitor。然后,調(diào)用statement的accept方法,將visitor對(duì)象傳入其中。這個(gè)accept方法會(huì)遍歷statement的AST節(jié)點(diǎn),并對(duì)每個(gè)節(jié)點(diǎn)調(diào)用相應(yīng)的visitor方法。在這個(gè)過(guò)程中,如果遇到了await表達(dá)式,就會(huì)調(diào)用_AwaitVisitor對(duì)象的visitAwaitExpression方法,將hasAwait屬性設(shè)置為true。
最后,函數(shù)返回visitor對(duì)象的hasAwait屬性,即表示給定的statement中是否含有await關(guān)鍵字。
一條簡(jiǎn)單的自定義規(guī)則就實(shí)現(xiàn)了,需要實(shí)現(xiàn)的有三點(diǎn):
1. 如何定位到context.read的token
2. 通過(guò)循環(huán)的方式向前遍歷,判斷是否有await
3. 通過(guò)AST遍歷方式判斷語(yǔ)句是否是await
規(guī)則二:使用as表達(dá)式前需要使用is判斷(完成對(duì)強(qiáng)制類(lèi)型校驗(yàn))
在 Dart 中,as 表達(dá)式用于將一個(gè)對(duì)象轉(zhuǎn)換為指定的類(lèi)型。如果對(duì)象不是指定類(lèi)型的實(shí)例,則會(huì)拋出一個(gè) TypeError 異常。此條規(guī)則也能很好的減少代碼異常。
as規(guī)則主要是檢查當(dāng)前node節(jié)點(diǎn)前面是否有符合is判斷的條件。
這里查找與context.read不同之處,除了向前查找,同時(shí)還會(huì)向上查找。同時(shí)由于涉及if判斷語(yǔ)句,整條規(guī)則的復(fù)雜度會(huì)上一個(gè)臺(tái)階。
圖片
這段遍歷代碼與之前有兩個(gè)不同點(diǎn):
1. If 語(yǔ)句內(nèi)的遍歷,這是正向遍歷,查找if(變量 is 類(lèi)型)校驗(yàn)類(lèi)型
2. 向前遍歷,這是逆向遍歷,查找if(變量 is! 類(lèi)型) return的校驗(yàn)
這里先看下else if (parent is IfStatement)分支中的isExpressionCheck()方法,這個(gè)方法主要作用是處理正向遍歷邏輯。
圖片
1. 先引入一個(gè)變量positiveCheck,代表正向和反向。下述情況滿(mǎn)足之一,變量都算類(lèi)型校驗(yàn)成功。
- 正向是if()語(yǔ)句包裹內(nèi)的,需要向上查找if (a is String) 條件;這里檢查if語(yǔ)句內(nèi),是正向。
- 反向是if...return 語(yǔ)句,需要向同級(jí)向前查找 if( is! ) return; 條件;checkStatements 查找的是取反的條件。
2. 變量isBang標(biāo)識(shí)是否有整個(gè)條件取反,例如:if (!(a != null)),為什么非引入這么個(gè)變量呢?
If (a == null)與if (!(a != null))的邏輯是一樣的,但I(xiàn)f (a == null && 其他條件)與if (!(a != null && 其他條件))這種邏輯就完全不同。
positiveCheck與isBang組合起來(lái)有以下4種情況:
If (a is String)
If (a is! String)
If (!(a is String))
If (!(a is! String))上述代碼正是解決此類(lèi)組合問(wèn)題, If (a is String) {a as String}與 If (a is! String) return; a as String。這兩種方式也屬于判斷了類(lèi)型。
解決了組合問(wèn)題,再看一個(gè)嵌套的問(wèn)題。
If 判斷的邏輯復(fù)雜,情況有多種。例如:
在正向情況下:
If (a is String && b is String) 是有效的
If (a is String || b is String) 是無(wú)效的在反向情況下:
If (a is! String && b is! String) return 是無(wú)效的
If (a is! String || b is! String) return 是有效的圖片
這段代碼能處理好if的條件,是因?yàn)樗ㄟ^(guò)遞歸的方式,深度遍歷if語(yǔ)句條件中的所有子表達(dá)式,找到其中是否包含is表達(dá)式。
在if語(yǔ)句中的條件表達(dá)式中,如果包含is表達(dá)式,則判斷條件表達(dá)式是否滿(mǎn)足positiveCheck或isBang參數(shù)的要求。如果滿(mǎn)足要求,則直接返回true,否則需要判斷if語(yǔ)句的then部分是否終止控制流,如果終止,則返回true,否則返回false。
因此,這段代碼能夠處理好if語(yǔ)句中的條件,以及其他語(yǔ)句中的表達(dá)式,判斷其中是否包含is表達(dá)式,并根據(jù)positiveCheck和isBang參數(shù)進(jìn)行判斷,最終返回判斷結(jié)果。
當(dāng)前結(jié)點(diǎn)與if條件結(jié)點(diǎn)比較:
圖片
函數(shù)首先調(diào)用addPropertyAccessTarget()函數(shù),將sourceExp和targetExp按照token分解成List
然后,函數(shù)調(diào)用isCompareList()函數(shù)比較sourceExpTarget和targetExpTarget是否相等。如果相等,則判斷positiveCheck和isBang參數(shù)去判斷!,如果滿(mǎn)足要求,則將isRes設(shè)置為true。
如果checksIsExpression比較成功:
1. positiveCheck為true,表示正向比較成功。
2. positiveCheck為false,則去判斷thenStatement的最后一條語(yǔ)句是否為return,bread,continue等關(guān)鍵字,如果是則為true,否則為false。
圖片
至此,一個(gè)正向的、逆向的is類(lèi)型判斷基本完成。但實(shí)際代碼還有一些特殊情況,例如:
解決了正向、反向;組合的問(wèn)題。在實(shí)際開(kāi)發(fā)中還遇到一些特殊情況,例如都是一個(gè)if條件、二元表達(dá)式、數(shù)據(jù)中的二元表達(dá)式等。這些解決思路與上述類(lèi)似。
3. 同一個(gè)if判斷,is在條件前面已經(jīng)判斷,可查看else if (parent is BinaryExpression)分支。
4. 二元表達(dá)式 ?:,可查看isConditionalExpressionCheck方法:
json['list'] is List ? json['list'] as List : []5. 數(shù)組中的判斷,[]中的token是IfElement,可查看else if (parent is ConditionalExpression || parent is IfElement)分支代碼:
[json['list'] is List ? json['list'] as List : []];這條自定義規(guī)則要復(fù)雜很多,難點(diǎn)在于:
- if判斷組合情況比較復(fù)雜,如何處理好組合情況是個(gè)難點(diǎn)。
- if語(yǔ)句會(huì)有邏輯運(yùn)算,怎么處理好這種情況值得思考。
- 還需要考慮一些特殊情況:例如二元表達(dá)式等。
規(guī)則三:使用強(qiáng)制解包!前需要if判空
在 Dart 語(yǔ)言中,使用 ! 符號(hào)進(jìn)行強(qiáng)制解包時(shí),如果對(duì)象為 null 會(huì)拋出 NoSuchMethodError 異常。因此,在使用 ! 操作符時(shí),我們需要確保變量或表達(dá)式不為空。這又是一個(gè)使用自定義規(guī)則很好解決的場(chǎng)景。
If 判空邏輯處理
If 語(yǔ)句的判空邏輯還是比較復(fù)雜,其主要難點(diǎn)在:
If該如何判空,a == null 是判空,a.isEmpty也是判空,a?.isEmpty也是判空,is String判斷也是判空。其復(fù)雜度會(huì)更高。
這里抽象了一個(gè)思想:不是去處理 a != null 或者 a?.isNotEmpty == true,還有isEmpty,靠方法去判空代碼就復(fù)雜了。而是按以下邏輯:
- rightOperand 是 null字面量且operator操作符是 !=
- 又或者rightOperand 是 非null字面量 操作符是 ==
圖片
讀者可以思考以下場(chǎng)景代碼能否校驗(yàn)成功:
if的變量對(duì)比邏輯也略有不同,例如:
If (a?.b != null) {} 這個(gè)時(shí)候變量a變量屬于判空。所以括號(hào)內(nèi)的變量是條件判空的子集。
if判空邏輯一些特殊情況
1. 判斷條件不再是單純的is判斷。下面是算法核心:
- 例如正向只有兩種情況, != null和== (!null),這種包括了 a != null、a?.isNotEmpty == true。逆向場(chǎng)景類(lèi)似。
/*
*判斷條件
*正向:
*1. != null
*2. == (!null)
*反向:
*1. == null
*2. != (!null)
*
*加!
*正向:
*1. !(== null)
*2. !(!= (!null))
*反向:
*1. !(!= null)
*2. !(== (!null))
* */2. 支持StringUtils工具類(lèi)判空,思路與上面類(lèi)型,可查看else if (check is MethodInvocation) 分支。
圖片
3. 支持is類(lèi)型判空,思路也是調(diào)用as的規(guī)則。
圖片
4. 支持contains判空,思路不贅述。
圖片
5. 支持條件提取為變量。
圖片
6. 支持前面使用了 = 或者 ??= 默認(rèn)為非空。
圖片
強(qiáng)制解包!的if判斷比as的更復(fù)雜:
1. 除了a == null、a != null等簡(jiǎn)單判空, a?.isNotEmpty == true,a?.isNotEmpty ?? true都是判空;相對(duì)于之前判空會(huì)更復(fù)雜。
2. 同時(shí)還需要支持StringUtils工具類(lèi)的判空;也囊括了 Is String的判空情況,特殊情況也會(huì)多。
3. 同時(shí)變量token與判空條件的token是子集的關(guān)系,這點(diǎn)與is稍有差異。
忽略注釋
這是一個(gè)非常好的應(yīng)用,理想情況下是所有代碼均可修改,但實(shí)際情況時(shí),有些代碼修改起來(lái)非常麻煩,又或者改動(dòng)之后影響不可評(píng)估,這個(gè)時(shí)候最好的辦法就是不修改,而忽略注釋正好解決這個(gè)問(wèn)題。
使用
當(dāng)有些不需要修改或者風(fēng)險(xiǎn)較大,可以使用//ignore:的方式來(lái)忽略報(bào)錯(cuò):
//ignore: avoid_use_as
//ignore: use_postfix_pre_need_if_empty1. 添加在類(lèi)的前一行:
圖片
2. 添加在方法的前一行:
圖片
3. 添加在報(bào)錯(cuò)節(jié)點(diǎn)的前一行或者當(dāng)前行:
圖片
實(shí)現(xiàn)思路
1. 遍歷給定的Dart編譯單元中的所有token;把單行注釋添加到_commentTokens中。
2. 在addError之前,判斷該報(bào)錯(cuò)node是否有ignore:忽略策略。
- 遍歷注釋節(jié)點(diǎn)行號(hào)
- 與當(dāng)前報(bào)錯(cuò)的node行號(hào)比較,如果差值等于0或者1,則查找成功,否則查找失敗
- node當(dāng)前所在函數(shù)的行號(hào)、所在類(lèi)的行號(hào)比較,差值等于1則查找成功,否則查找失敗
圖片
三、總結(jié)
本文主要介紹了自定義代碼分析插件工程的搭建及由易到難實(shí)現(xiàn)了3個(gè)自定義代碼分析插件的規(guī)則,解決了人工CR的效率低、容易遺漏這一問(wèn)題。
代碼開(kāi)發(fā)過(guò)程中遭遇很多挑戰(zhàn),網(wǎng)上關(guān)于自定義代碼分析文章幾乎為0,能搜索到只是一些對(duì)linter的簡(jiǎn)單配置。也希望本文給讀者啟發(fā),少走彎路。
后續(xù)會(huì)實(shí)現(xiàn)更多的規(guī)則,來(lái)規(guī)范團(tuán)隊(duì)內(nèi)的代碼,減少人工CR的工作量。同時(shí)分享自定義規(guī)則的實(shí)現(xiàn),使得每個(gè)成員都能進(jìn)行自定義規(guī)則的實(shí)現(xiàn)。
文章名稱(chēng):Flutter代碼靜態(tài)檢查原理與應(yīng)用
當(dāng)前鏈接:http://www.dlmjj.cn/article/cojdgis.html


咨詢(xún)
建站咨詢(xún)
