background

Lint is a static code inspection tool provided by Google that scans for potential problems in code and alerts developers. In addition to the hundreds of Lint rules native to Android, you can also customize Lint rules using the API of the Lint framework.

Custom Lint rules can specify different scanning rules based on project requirements. For example: coding specification, code style, specific problem checking, etc.

With custom checking rules, you can standardize code writing when submitting code. But there is a problem: new code uses new specifications, but how do you resolve the old code style in your project? With old code, you can’t change every line of code. This is the time for targeted checks, that is, the use of incremental checks.

Incremental code review has several benefits

  • Avoid some of the problems of modifying “ancestral” code. If the full scan, a lot of old code problems will be exposed, which greatly increases the workload, developers do not have so much energy to modify all;
  • Some code specifications have been added to make them mandatory. Incremental scanning code, which rolls out if the code has problems, can force developers to standardize the code;

Implement Lint incremental code checking tools

Here the Lint incremental code checking tool is implemented as a Gradle plug-in. You only need to reference the plug-in in your project to use the Lint tool. The main implementation is that in git commit scenarios, new code is checked (in units of behavior) every time the code is committed. If there is code that does not conform to Lint’s custom rules, the commit is rolled back.

Incremental code review process

The Lint incremental code checking tool is implemented using the Git hooks(post-commit) + Lint framework.

Git hooks: are scripts that respond to git actions, equivalent to a callback. Performing specific Git actions triggers specific Git hooks script execution.

The Lint framework is the basis for implementing Lint scanning. Perform Lint scanning using apis provided by the Lint framework.

  1. Commit (git diff)
  2. Trigger Git hooks (post-commit) script execution. Start Lint checking by executing a Lint checking task in a script (this task is a Gradle task);
  3. Create LintRequest (mainly to specify files that Lint will scan);
  4. Get the delta code (use git diff to find the modified line number);
  5. Start Lint checking;
  6. After checking the output, if there is any code that does not conform to the rules, the submission will be rolled back.

The implementation of Lint incremental checks

After reading the above process, you may feel confused. Here is a detailed breakdown of each step. The first step in implementing Lint delta code checking is to get the delta code to be committed. The current solution is to obtain relevant data through git commands.

Execution of Git hooks

The current scenario is to trigger the check flow by using a post-commit script. The post-commit script is executed after the Git commit.

Gets the file that LInt checks

Get delta file

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

Run the git diff command to obtain the file.

/** * Run the Git command to get the files you want to check@param project gradle.Project
 * @returnFile list */
List<String> getCommitChange(Project project) {
    ArrayList<String> filterList = new ArrayList<>()
    try {
        Git commit git commit git commit git commit git commit 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
    }
}
Copy the code

Get delta code

This is a critical step, because it takes the line number of the delta code and uses that line number data to implement delta checking.

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

Filepath is the relative path of the incremental file.

Once the data is ready, the rest of the work is left to the Lint framework. Next, start the Lint check operation.

Git diff ** * git diff ** git diff **@paramFilePath filePath *@paramProject Project object *@paramStartIndex modifies the starting table array *@paramEndIndex changes the end of the following table array */
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()
    }
}
Copy the code

Performing Lint checks

The main classes in the Lint framework describe:

  • LintCliClient: Lint client that integrates lint-checked actions, related configurations, and entry points for Lint checking.
  • LintCliFlags: Lint flag bit management class that provides flag bits for Lint operations. The Lint code checker mainly uses the log-generating configuration in this class, and can implement different output formats (such as TXT, XML, HTML, etc.) by adding report generation classes for different implementations.
  • LintRequest: A request class that performs Lint operations to store files that Lint will scan. Checking for incremental files can be done by overriding the LintRequest initialization method in the Lint tool.
  • LintDriver: Class that implements Lint rule checking logic.
  • IssueRegistry: Custom Lint rule management class. Use to add Lint custom rules.

The above introduction to the classes in the Lint framework are the main classes used in implementing Lint incremental code checking.

Create LintRequest

class LintToolClient extends LintCliClient {

    @Override
    /** * createLintRequest */ by overriding the createLintRequest method
    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)
    }
}
Copy the code

The above code is the creation of a LintRequest by overriding the createLintRequest method in LintCliClient. The parameter files is the file to be checked.

Lint checks the execution logic

/*LintCliClient*/
public int run(@NonNull IssueRegistry registry, @NonNull List<File> files) throws IOException {
        assert! flags.getReporters().isEmpty();this.registry = registry; //Lint customizes check rules

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

        addProgressPrinter();
        validateIssueIds();

        driver.analyze(); // Perform Lint checks

        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;
    	// Prints Lint Reports according to Reports in LintCliFlags
        for (Reporter reporter : flags.getReporters()) {
            reporter.write(stats, warnings);
            if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
                hasConsoleOutput = true; }}/ /... Omit some code..............

        return flags.isSetExitCode() ? (hasErrors ? ERRNO_ERRORS : ERRNO_SUCCESS) : ERRNO_SUCCESS;
    }
Copy the code

The logic of the above code is the main process by which Lint performs checks. As you can see, you pass in the custom Lint rule IssueRegistry in your code, then create a LintRequest, then start Lint checking, and finally print out the results. The results are printed to Reports added to LintCliFlags.

Implementation of incremental code checking

// Outputs a Lint check report
for (Reporter reporter : flags.getReporters()) {
            reporter.write(stats, warnings);
            if (reporter instanceof TextReporter && ((TextReporter)reporter).isWriteToConsole()) {
                hasConsoleOutput = true; }}Copy the code

According to the logic of the code above, is the process by which Lint checks the output. The implementation of incremental code check is to rewrite Reporter class and implement custom output rules in the rewrite class. The implementation method here is to use the file modification line number obtained by git command above to filter, so as to achieve the effect of incremental check.

conclusion

The implementation of Lint’s incremental code checking tool is described above. The key to implementing incremental code checking is to get the exact location of file changes so that the output can be filtered.

The advantage of incremental code checking over regular Lint checking is that it avoids conflicts between old code and new rules, and that using git in conjunction with it can add some enforcement to code submission.

Finally, Lint’s custom rules are used in the Lint incremental code tool. These can also be used as extensions to the native Lint rules at the writing stage, with the same effect as the original Lint rules.

Those interested in implementing the Lint incremental code tool can access the source code on Github, or star it.

Lint delta code check tool link