Android自定義Lint增量代碼檢查工具

背景

Lint是Google提供的一個靜態代碼檢查工具,能夠掃描出代碼中潛在的問題,而且會對開發人員作出提示。並且除了Android原生提供的幾百種Lint規則之外,還可使用Lint框架的API自定義Lint規則。git

自定義Lint規則能夠根據項目需求制定不一樣的掃描規則。好比:編碼規範、代碼風格、特定問題檢查等。github

有了自定義檢查規則,提交代碼的時候能夠規範代碼編寫。可是還有一個問題,新的代碼使用新的規範,但如何解決項目中老代碼風格?對於老代碼,不可能每行代碼都要修改。這個時候就要有針對性的檢查,那就是使用增量檢查。數組

增量代碼檢查有如下幾個好處框架

  • 避免修改「祖傳」代碼的一些問題。若是全量掃描,以前老代碼的問題一大堆就會暴露出來, 這樣就大大增長了工做量,開發人員也沒有那麼多的精力所有修改;
  • 增長了一些代碼規範的強制性。增量掃描代碼,若是代碼有問題,就會滾,能夠強制開發人 員規範代碼;

實現Lint增量代碼檢查工具

這裏Lint增量代碼檢查工具是以Gradle插件的方式實現的。只須要在項目中引用插件即可使用Lint工具。主要實現的功能是在git提交場景下,每次提交代碼都會檢查新增的代碼(以行爲單位)。若是代碼中存在不符合Lint自定義規則的代碼,就回滾本次提交。ide

增量代碼檢查流程

Lint增量代碼檢查工具使用git hooks(post-commit) + Lint框架實現。工具

git hooks:是用來響應git的操做的腳本,至關於一個回調。執行特定的git操做會出發特定的git hooks腳本執行。post

Lint框架:是實現Lint掃描的基礎。利用Lint框架提供的API執行Lint掃描。gradle

  1. 提交(git commit)本次修改代碼(經過git diff命令找出提交的文件);
  2. 觸發git hooks(post-commit)腳本執行。在腳本中執行Lint檢查任務(該任務是gradle任務)開始Lint檢查;
  3. 建立LintRequest(主要做用是指定Lint將要掃描的文件);
  4. 獲取增量代碼(經過git diff找出修改的行號);
  5. 開始Lint檢查;
  6. 檢查完畢輸出結果,若是有不符合規則的代碼,將回退本次提交。

Lint增量檢查實現原理

看完上面的流程,可能會以爲不明因此。這裏針對各個步驟作出詳細解析。在實現Lint增量代碼檢查的過程當中,首要的步驟就是獲取將要提交的增量代碼。目前的解決方案是經過git命令獲取相關數據。this

git hooks的執行

目前的方案是經過使用post-commit腳本觸發檢查流程。post-commit腳本是在git commit以後執行。編碼

獲取LInt檢查的文件

獲取增量文件

git diff --name-only --diff-filter=ACMRTUXB HEAD~1 HEAD~0

經過git diff命令獲取本次提交的文件。

/** * 經過Git命令獲取須要檢查的文件 * * @param project gradle.Project * @return 文件列表 */
List<String> getCommitChange(Project project) {
    ArrayList<String> filterList = new ArrayList<>()
    try {
        //此命令獲取本次提交的文件 在git commit以後執行
        String command = "git diff --name-only --diff-filter=ACMRTUXB HEAD~1 HEAD~0"
        String changeInfo = command.execute(null, project.getRootDir()).text.trim()
        if (changeInfo == null || changeInfo.empty) {
            return filterList
        }

        String[] lines = changeInfo.split("\\n")
        return lines.toList()
    } catch (Exception e) {
        e.printStackTrace()
        return filterList
    }
}
複製代碼

獲取增量代碼

這是關鍵的一步,由於這一步要獲取增量代碼所在的具體行號,經過使用這些行號數據實現增量檢查的效果。

git diff --unified=0 --ignore-blank-line --ignore-all-space HEAD~1 HEAD filepath

filepath就是增量文件的相對路徑。

數據準備完畢之後,剩下的工做就要交給Lint框架了。接下來開始執行Lint檢查操做。

/** * 經過git diff獲取已提交文件的修改,包括文件的添加行的行號、刪除行的行號、修改行的行號 * * @param filePath 文件路徑 * @param project Project對象 * @param startIndex 修改開始的下表數組 * @param endIndex 修改結束的下表數組 */
void getFileChangeStatus(String filePath, Project project, List<Integer> startIndex, List<Integer> endIndex) {
    try {
        String command = "git diff --unified=0 --ignore-blank-lines --ignore-all-space HEAD~1 HEAD " + filePath
        String changeInfo = command.execute(null, project.getRootDir()).text.trim()
        String[] changeLogs = changeInfo.split("@@")
        String[] indexArray for (int i = 1; i < changeLogs.size(); i += 2) {
            indexArray = changeLogs[i].trim().split(" ")
            try {
                int start, end
                String[] startArray = null if (indexArray.length > 1) {
                    startArray = indexArray[1].split(",")
                }

                if (startArray != null && startArray.length > 1) {
                    start = Integer.parseInt(startArray[0])
                    end = Integer.parseInt(startArray[0]) + Integer.parseInt(startArray[1])
                } else {
                    start = Integer.parseInt(startArray[0])
                    end = start + 1
                }
                startIndex.add(start)
                endIndex.add(end)
            } catch (NumberFormatException e) {
                e.printStackTrace()
                startIndex.add(0)
                endIndex.add(0)
            }

        }
    } catch (Exception e) {
        e.printStackTrace()
    }
}
複製代碼

執行Lint檢查

Lint框架中的主要類說明:

  • LintCliClient:Lint客戶端,做用是集成lint檢查的操做、相關配置以及lint檢查的入口。
  • LintCliFlags:Lint標誌位管理類,提供了Lint操做的標誌位。Lint代碼檢查工具主要使用了該類中生成日誌的配置,經過加入不一樣實現的報告生成類能夠實現不一樣的輸出格式(好比TXT、XML、HTML等)。
  • LintRequest:執行Lint操做時的一個請求類,主要做用是存儲Lint將要掃描的文件。在Lint工具中重寫LintRequest初始化方法能夠實現增量文件的檢查。
  • LintDriver:執行Lint規則檢查邏輯的類。
  • IssueRegistry:自定義Lint規則管理類。用於添加Lint自定義規則。

上述對於Lint框架中類的介紹是在實現Lint增量代碼檢查中主要用到的類。

建立LintRequest

class LintToolClient extends LintCliClient {

    @Override
    /** * 經過重寫createLintRequest方法建立LintRequest */
    protected LintRequest createLintRequest(List<File> files) {
        LintRequest request = super.createLintRequest(files)
        for (Project project : request.getProjects()) {
            for (File file : files) {
                project.addFile(file)
            }
        }
        return new LintRequest(this, files)
    }
}
複製代碼

上面的代碼就是LintRequest的建立過程,經過重寫LintCliClient中的createLintRequest方法。其中參數files就是將要檢查的文件。

Lint檢查的執行邏輯

/*LintCliClient*/
public int run(@NonNull IssueRegistry registry, @NonNull List<File> files) throws IOException {
        assert !flags.getReporters().isEmpty();
        this.registry = registry; //Lint自定義檢查規則

        LintRequest lintRequest = createLintRequest(files); //建立LintRequest
        driver = createDriver(registry, lintRequest); //建立LintDriver

        addProgressPrinter();
        validateIssueIds();

        driver.analyze(); //執行Lint檢查

        Collections.sort(warnings);

        int baselineErrorCount = 0;
        int baselineWarningCount = 0;
        int fixedCount = 0;

        LintBaseline baseline = driver.getBaseline();
        if (baseline != null) {
            baselineErrorCount = baseline.getFoundErrorCount();
            baselineWarningCount = baseline.getFoundWarningCount();
            fixedCount = baseline.getFixedCount();
        }

        Stats stats = new Stats(errorCount, warningCount,
                baselineErrorCount, baselineWarningCount, fixedCount);

        boolean hasConsoleOutput = false;
    	//根據LintCliFlags中的Reports打印Lint報告
        for (Reporter reporter : flags.getReporters()) {
            reporter.write(stats, warnings);
            if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
                hasConsoleOutput = true;
            }
        }
    
    //............省略部分代碼..............

        return flags.isSetExitCode() ? (hasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
    }
複製代碼

上面代碼的邏輯就是Lint執行檢查的主要過程。能夠看到,在代碼中先傳入自定義的Lint規則IssueRegistry,而後建立LintRequest,接下就開始執行Lint檢查,最後將結果輸出。結果輸出到添加在LintCliFlags的Reports中。

增量代碼檢查的實現

//輸出Lint檢查報告
for (Reporter reporter : flags.getReporters()) {
            reporter.write(stats, warnings);
            if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
                hasConsoleOutput = true;
            }
        }
複製代碼

根據上面代碼的邏輯,是Lint檢查輸出結果的過程。增量代碼檢查的實現就是重寫Reporter類,在重寫的類中實現自定義的輸出規則,這裏的實現方法就是使用上文中經過git命令獲取的文件修改行號進行過濾,從而實現增量檢查的效果。

總結

上面描述了Lint增量代碼檢查工具的實現過程,實現增量代碼檢查的關鍵就是獲取文件修改的精確位置,以便在輸出結果是進行過濾。

增量代碼檢查相較於常規的Lint檢查,好處就是可以避免老代碼的與新規則的衝突,同時結合git使用可以在提交代碼時增長一些強制性。

最後,Lint增量代碼工具中使用的是Lint的自定義規則。這些還能夠做爲原生的Lint規則的擴展,在代碼編寫的階段使用,效果跟原聲Lint規則一致。

對Lint增量代碼工具的實現感興趣的同窗,能夠在github上獲取源碼,感興趣的能夠star一下。

Lint增量代碼檢查工具連接

相關文章
相關標籤/搜索