The craftsman may be late, but he is never absent. Finally, he decided to share his cloud notes

background

We all know that there are many static code checking tools, such as Ali’s P3C, Sonar Hook and a bunch of plugins. However, these tools are not friendly to an existing project, because old code can be scanned with a lot of problems and repair costs are very high, so these tools are suitable for new projects or early intervention, and are not very painful for old projects.

Therefore, it is necessary to do incremental checks; One is to increment changed for version control, which involves a full accountability issue for old file changes. The other is to increment the new file, which guarantees the constraint of the new file from this point forward.

Because it is difficult to ensure standardization only by people’s consciousness, even review code will always be negligent, so it is necessary to make static code inspection convenient for development and test acceptance, so it needs secondary automatic check, so I finally choose Checkstyle for customization after investigation.

After customization can be done local IDE development real-time new file specification prompt, CI building machine through gradle task to generate reports; A proactive canonicalization and a mandatory reporting test are strictly implemented and are not allowed to enter the test process until the repair is complete.

checkstyle

What is it? How does it work? Can do? I won’t go into details here, but focus on where the source code and configuration can be obtained.

  • Checkstyle official document
  • Checkstyle source
  • Checkstyle IDEA plugin source code
  • Checkstyle Gradle plugin

By analyzing the main processes and documents above, we made a simple secondary customization to adapt to the standard check of newly added files, including gradle new file check and IDEA plug-in new file check, as shown below.

Gradle script incremental customization

Gradle uses the checkstyle plugin to create a file increment check.

. applyplugin: 'checkstyle'

def checkStyleFile = rootProject.file("checks.xml")
def reportHtmlFile = rootProject.file(project.buildDir.path + "/checkStyle/result.html")
def reportXmlFile = rootProject.file(project.buildDir.path + "/checkStyle/result.xml")

checkstyle {
    toolVersion "8.12"
    configFile checkStyleFile
    showViolations = true
    ignoreFailures = true
}

tasks.withType(Checkstyle) {
    classpath = files()
    source "${project.rootDir}"

    exclude '**/build/**'
    exclude '**/androidTest/**'
    exclude "**/test/**"

    reports {
        html {
            enabled true
            destination reportHtmlFile
        }
        xml {
            enabled true
            destination reportXmlFile
        }
    }
}

task checkstyleAll(type: Checkstyle) {
}

task checkstyleVcsChanged(type: Checkstyle) {
    def changedFiles = getFilterFiles('dr')
    if (changedFiles.isEmpty()) {
        enabled = false
    }
    include changedFiles
}

task checkstyleVcsNew(type: Checkstyle) {
    def changedFiles = getFilterFiles('A')
    if (changedFiles.isEmpty()) {
        enabled = false
    }
    include changedFiles
}

def getFilterFiles(diffFilter) {
    def files = getChangedFiles(diffFilter)
    if (files.isEmpty()) {
        return files
    }

    List<String> filterFiles = new ArrayList<>()
    for (file in files) {
        if (file.endsWith(".java") || file.endsWith(".xml")) {
            filterFiles.add(file)
        }
    }

    println("\nDiff need checkstyle filter "+diffFilter+"- >"+filterFiles)
    return filterFiles
}

def getChangedFiles(diffFilter) {
    def targetBranch = System.getenv("ghprbTargetBranch")
    def sourceBranch = System.getenv("ghprbSourceBranch")
    println("Target branch: $targetBranch")
    println("Source branch: $sourceBranch")

    List<String> files = new ArrayList<>()
    if (targetBranch.isEmpty()) {
        return files
    }

    def systemOutStream = new ByteArrayOutputStream()
    def command = "git diff --name-status --diff-filter=$diffFilter... $targetBranch $sourceBranch"
    command.execute().waitForProcessOutput(systemOutStream, System.err)
    def allFiles = systemOutStream.toString().trim().split('\n')
    systemOutStream.close()

    Pattern statusPattern = Pattern.compile("(\\w)\\t+(.+)")
    for (file in allFiles) {
        Matcher matcher = statusPattern.matcher(file)
        if (matcher.find()) {
            files.add(matcher.group(2))
        }
    }
    println("\nDiff filter "+diffFilter+"- >"+files)
    files
}
......
Copy the code

With the task above we can generate check reports from the command line. In Android development, we can automatically insert this task into the build process as follows:

. project.afterEvaluate { preBuild.dependsOn'checkstyleVcsNew' // The incremental task name}...Copy the code

The output can then be reported automatically on the build machine for testing and repair.

IDEA plug-in incremental customization

Fortunately, checkStyle has an IDEA plugin, and Android Studio is based on IDEA, so we can customize this plugin to support the new file increment check. The custom code is as follows:

/** * There is too much code in the old project to fix all formatting checks at once, so all new Java files will be rigorously formatted. * Only VCS version control projects are supported. * implement this document reference checkstyle - IDEA plugin source CheckStyleInspection. Java classes * /
public class CheckStyleVcsNewInspection extends LocalInspectionTool {

    private static final Logger LOG = Logger.getInstance(CheckStyleVcsNewInspection.class);
    private static final List<Problem> NO_PROBLEMS_FOUND = Collections.emptyList();

    private final CheckStyleInspectionPanel configPanel = new CheckStyleInspectionPanel();

    private CheckStylePlugin plugin(final Project project) {
        final CheckStylePlugin checkStylePlugin = project.getComponent(CheckStylePlugin.class);
        if (checkStylePlugin == null) {
            throw new IllegalStateException("Couldn't get checkstyle plugin");
        }
        return checkStylePlugin;
    }

    @Nullable
    public JComponent createOptionsPanel(a) {
        return configPanel;
    }

    @Override
    public ProblemDescriptor[] checkFile(@NotNull final PsiFile psiFile,
                                         @NotNull final InspectionManager manager,
                                         final boolean isOnTheFly) {
        if (isVcsNewFile(psiFile)) {
            final Module module = moduleOf(psiFile);
            return asProblemDescriptors(asyncResultOf(() -> inspectFile(psiFile, module, manager), NO_PROBLEMS_FOUND), manager);
        }
        return null;
    }

    // key, you need other incremental forms of their own change here!!
    private boolean isVcsNewFile(PsiFile psiFile) {
        if (psiFile == null) {
            return false;
        }

        final FileStatus fileStatus = FileStatusManager.getInstance(psiFile.getProject()).getStatus(psiFile.getVirtualFile());
        boolean ret = fileStatus == FileStatus.UNKNOWN || fileStatus == FileStatus.ADDED;
        LOG.debug(" VCS file "+psiFile.getName()+" status: "+ret+", fileStatus="+fileStatus.getText());
        return ret;
    }

    @Nullable
    private Module moduleOf(@NotNull final PsiFile psiFile) {
        return ModuleUtil.findModuleForPsiElement(psiFile);
    }

    @Nullable
    public List<Problem> inspectFile(@NotNull final PsiFile psiFile,
                                     @Nullable final Module module.@NotNull final InspectionManager manager) {
        LOG.debug("Inspection has been invoked.");

        final CheckStylePlugin plugin = plugin(manager.getProject());

        ConfigurationLocation configurationLocation = null;
        final List<ScannableFile> scannableFiles = new ArrayList<>();
        try {
            configurationLocation = plugin.getConfigurationLocation(module.null);
            if (configurationLocation == null || configurationLocation.isBlacklisted()) {
                return NO_PROBLEMS_FOUND;
            }

            scannableFiles.addAll(ScannableFile.createAndValidate(singletonList(psiFile), plugin, module));

            return checkerFactory(psiFile.getProject())
                    .checker(module, configurationLocation)
                    .map(checker -> checker.scan(scannableFiles, plugin.configurationManager().getCurrent().isSuppressErrors()))
                    .map(results -> results.get(psiFile))
                    .map(this::dropIgnoredProblems)
                    .orElse(NO_PROBLEMS_FOUND);

        } catch (ProcessCanceledException | AssertionError e) {
            LOG.debug("Process cancelled when scanning: " + psiFile.getName());
            return NO_PROBLEMS_FOUND;

        } catch (CheckStylePluginParseException e) {
            LOG.debug("Parse exception caught when scanning: " + psiFile.getName(), e);
            return NO_PROBLEMS_FOUND;

        } catch (Throwable e) {
            handlePluginException(e, psiFile, plugin, configurationLocation, manager.getProject());
            return NO_PROBLEMS_FOUND;

        } finally{ scannableFiles.forEach(ScannableFile::deleteIfRequired); }}private List<Problem> dropIgnoredProblems(final List<Problem> problems) {
        returnproblems.stream() .filter(problem -> problem.severityLevel() ! = SeverityLevel.Ignore) .collect(toList()); }private void handlePluginException(final Throwable e,
                                       final @NotNull PsiFile psiFile,
                                       final CheckStylePlugin plugin,
                                       final ConfigurationLocation configurationLocation,
                                       final @NotNull Project project) {
        if(e.getCause() ! =null && e.getCause() instanceof FileNotFoundException) {
            disableActiveConfiguration(plugin, project);

        } else if(e.getCause() ! =null && e.getCause() instanceof IOException) {
            showWarning(project, message("checkstyle.file-io-failed"));
            blacklist(configurationLocation);

        } else {
            LOG.warn("CheckStyle threw an exception when scanning: "+ psiFile.getName(), e); showException(project, e); blacklist(configurationLocation); }}private void disableActiveConfiguration(final CheckStylePlugin plugin, final Project project) {
        plugin.configurationManager().disableActiveConfiguration();
        showWarning(project, message("checkstyle.configuration-disabled.file-not-found"));
    }

    private void blacklist(final ConfigurationLocation configurationLocation) {
        if(configurationLocation ! =null) { configurationLocation.blacklist(); }}@NotNull
    private ProblemDescriptor[] asProblemDescriptors(final List<Problem> results, final InspectionManager manager) {
        return ofNullable(results)
                .map(problems -> problems.stream()
                        .map(problem -> problem.toProblemDescriptor(manager))
                        .toArray(ProblemDescriptor[]::new))
                .orElseGet(() -> ProblemDescriptor.EMPTY_ARRAY);
    }

    private CheckerFactory checkerFactory(final Project project) {
        returnServiceManager.getService(project, CheckerFactory.class); }}Copy the code

Add the following configuration to the plugin configuration file:

<localInspection implementationClass="org.infernus.idea.checkstyle.CheckStyleVcsNewInspection"
    bundle="org.infernus.idea.checkstyle.CheckStyleBundle"
    key="inspection.cvs.display-name"
    groupKey="inspection.group"
    level="WARNING"
    enabledByDefault="true"/>
Copy the code

Recompile and publish the plug-in, and you can customize it if you want to change it. About how to develop IDEA plug-in, later have the opportunity to introduce it.

After installing the plugin, we can do the specification check for incremental new files as follows:

Then we can use it happily. The specific effect is referred to the original Checkstyle idea Plugin, but with more incremental operations.

conclusion

At this point, a complete set of incremental code specification checks are completed, and the specification rules can continue to be written in checkStyle’s XML by themselves, but be aware of version-compatibility issues with attributes in the XML, as described in the official documentation.

Of course, if you feel that checking only new files is not appropriate, you can make your own configuration changes to the changed section of the core code above, such as checking all changed files, or specifying whitelisted directories, depending on your team’s needs.

At this point, the code specification was checked, and the main problem was the git baseline branch name acquisition problem. In addition to the standard CI, I referred to many methods and official documents, but failed to find a good solution. The solutions found could only work in the general branch structure, which would become chaotic after the structure was complicated. Do not know which big guy has a good suggestion, welcome to give advice or two.

Artisans if the water without permission is strictly prohibited to be reproduced, please respect the author’s work.