自动修复非格式化但简单的CheckStyle问题

时间:2017-07-26 12:16:24

标签: java checkstyle

是否有一个命令行工具可以自动修复非格式化但在Java源代码中仍然看似简单的CheckStyle问题,如:

  • 避免内联条件
  • 使“xxx”成为静态方法

我知道fix formatting有各种各样的工具,有些IDE有相当高级的quick fixers但是到目前为止我找不到任何可以在源代码文件夹上递归运行或集成在提交钩子中的东西

1 个答案:

答案 0 :(得分:1)

听起来像是一个很好的挑战,但我也找不到能够做到这一点的自动工具。如您所述,有很多选项可以更改代码格式。对于其他小问题,您可以从命令行运行Checkstyle并过滤掉可修复的警告。用于解析和更改Java源代码的库可以帮助实际进行更改,例如JavaParser。也许您可以使用Java源代码操作工具(如JavaParser)在相对较短的时间内编写自定义工具。

(还有其他工具,例如ANTLR可以使用;请参阅Stack Overflow上的这个问题的更多想法:Java: parse java source code, extract methods。有些像RoasterJavaPoet这样的库不解析方法体,这使得它们在这种情况下不太适合。)

作为一个非常简单的示例,假设我们有一个小型Java类,Checkstyle会为其生成两条消息(使用仅检查checkstyle-checks.xmlFinalParameters的简约FinalLocalVariable Checkstyle配置文件:

// Example.java:

package q45326752;

public class Example {
    public static void main(String[] arguments) {
        System.out.println("Hello Checkstyle...");

        int perfectNumber = 1 + 2 + 3;

        System.out.println("Perfect number: " + perfectNumber);
    }
}


Checkstyle warnings:

java -jar checkstyle-8.0-all.jar -c checkstyle-checks.xml Example.java
[ERROR] Example.java:4:29: Parameter arguments should be final. [FinalParameters]
[ERROR] Example.java:7:13: Variable 'perfectNumber' should be declared final. [FinalLocalVariable]

使用JavaParser,这两个警告可以像这样自动修复(代码试图证明这个想法;现在已经忽略了一些部分):

// AutomaticCheckstyleFix.java:

package q45326752;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;

import java.io.File;
import java.io.FileNotFoundException;

public class AutomaticCheckstyleFix {
    private MethodDeclaration bestMatchMethod;
    private int bestMatchMethodLineNumber;
    private Statement statementByLineNumber;

    public static void main(final String[] arguments) {
        final String filePath = "q45326752\\input\\Example.java";

        try {
            new AutomaticCheckstyleFix().fixSimpleCheckstyleIssues(new File(filePath));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void fixSimpleCheckstyleIssues(File file) throws FileNotFoundException {
        CompilationUnit javaClass = JavaParser.parse(file);
        System.out.println("Original Java class:\n\n" + javaClass);
        System.out.println();
        System.out.println();

        // Example.java:4:29: Parameter arguments should be final. [FinalParameters]
        MethodDeclaration methodIssue1 = getMethodByLineNumber(javaClass, 4);
        if (methodIssue1 != null) {
            methodIssue1.getParameterByName("arguments")
                  .ifPresent(parameter -> parameter.setModifier(Modifier.FINAL, true));
        }

        // Example.java:7:13: Variable 'perfectNumber' should be declared final.
        // [FinalLocalVariable]
        Statement statementIssue2 = getStatementByLineNumber(javaClass, 7);
        if (statementIssue2 instanceof ExpressionStmt) {
            Expression expression = ((ExpressionStmt) statementIssue2).getExpression();
            if (expression instanceof VariableDeclarationExpr) {
                ((VariableDeclarationExpr) expression).addModifier(Modifier.FINAL);
            }
        }

        System.out.println("Modified Java class:\n\n" + javaClass);
    }

    private MethodDeclaration getMethodByLineNumber(CompilationUnit javaClass,
                                                    int issueLineNumber) {
        bestMatchMethod = null;

        javaClass.getTypes().forEach(type -> type.getMembers().stream()
                .filter(declaration -> declaration instanceof MethodDeclaration)
                .forEach(method -> {
                    if (method.getTokenRange().isPresent()) {
                        int methodLineNumber = method.getTokenRange().get()
                                .getBegin().getRange().begin.line;
                        if (bestMatchMethod == null
                                || (methodLineNumber < issueLineNumber
                                && methodLineNumber > bestMatchMethodLineNumber)) {
                            bestMatchMethod = (MethodDeclaration) method;
                            bestMatchMethodLineNumber = methodLineNumber;
                        }
                    }
                })
        );

        return bestMatchMethod;
    }

    private Statement getStatementByLineNumber(CompilationUnit javaClass,
                                               int issueLineNumber) {
        statementByLineNumber = null;

        MethodDeclaration method = getMethodByLineNumber(javaClass, issueLineNumber);
        if (method != null) {
            method.getBody().ifPresent(blockStmt
                    -> blockStmt.getStatements().forEach(statement
                    -> statement.getTokenRange().ifPresent(tokenRange -> {
                if (tokenRange.getBegin().getRange().begin.line == issueLineNumber) {
                    statementByLineNumber = statement;
                }
            })));
        }

        return statementByLineNumber;
    }
}

另一种方法可能是根据您尝试创建自动修复的插件来创建新的Checkstyle插件。也许您有足够的信息可用于不仅发出警告,而且还生成修复了这些问题的修改版本。

就个人而言,我会毫不犹豫地在提交时自动修复问题。当有许多简单的修复方法时,欢迎自动化,但我想在提交之前检查这些更改。运行这样的工具并检查更改可能是解决许多简单问题的一种非常快捷的方法。

我认为可以自动修复一些检查: