在SonarQube中限制每行相同检查的问题

时间:2017-12-22 10:13:22

标签: java plugins sonarqube

我正在使用Java Plugin API为SonarQube 5.6.6开发一个插件。我已经创建了一些自定义规则(检查),其中一个在同一行中多次报告相同的问题。这是有道理的,因为该行多次具有相同的错误但是,有没有办法限制这个,所以SonarQube只显示该行的问题?

图像(和代码)比单词更响亮,所以我将展示一个示例:每次检测到新类时都会报告问题的检查。

@Rule(key = "foo_key", name = "Foo issue", description = "Foo issue", priority = Priority.INFO)
public class FooCheck extends IssuableSubscriptionVisitor {

    @Override
    public List<Kind> nodesToVisit() {
        return ImmutableList.of(Kind.NEW_CLASS);
    }

    @Override
    public void visitNode(Tree tree) {
        reportIssue(tree, "New class!");
    }

}

因此,我们会在Foo foo = new Foo(new Bar());

这样的行中遇到两个问题

The same issue in the same line

我知道我可以更改此特定检查以实现我想要的效果。例如,我可以避免在分析NEW_CLASS节点时报告问题,如果它的任何参数是另一个NEW_CLASS;这样,我们会在节点new Bar()上报告一个问题,但在节点new Foo(new Bar())上没有报告,所以我们只会遇到一个问题:

@Override
public void visitNode(Tree tree) {
    final NewClassTree newClassTree = (NewClassTree) tree;

    if (newClassTree.arguments().stream().noneMatch(arg -> arg.is(Kind.NEW_CLASS))) {
        reportIssue(tree, "New class!");
    }
}

但是,这只是这项检查的解决方案。我想知道是否有一般的方法告诉SonarQube不要显示每行相同检查的几个问题。

感谢。

2 个答案:

答案 0 :(得分:2)

您可以在报告问题之前获取tree节点的行号,将其保存到全局变量lastLineReported或报告的行列表。然后,您可以通过两种方式检查简单的if语句:

1 - 使用lastLineReported变量:

if(lastLineReported != currentLine) {
        lastLineReported = currentLine;
        reportIssue(tree, "New class!");
    }

2 - 使用报告的行列表:

if(!reportedLines.contains(currentLine)) {
        reportedLines.add(currentLine);
        reportIssue(tree, "New class!");
    }

答案 1 :(得分:1)

基于Rafael Costa's answer,我将提供完整的解决方案:

关键是接口Treeorg.sonar.plugins.java.api.tree.Tree)有一个方法firstToken(),它返回SyntaxToken,这是另一个具有方法line()的接口。因此,我们可以调用tree.firstToken().line()来获取我们正在访问的节点的行,在我们第一次在该行上报告问题时保存它,并在以后访问节点时检查它们的行是否已报告问题

注意:我们不能在集合中静态保存这些行,因为每次访问要分析的每个源代码文件的节点时都会共享此集合的值。相反,我们必须保存每一行以及我们正在分析的文件。如果我们没有这样做并且我们在文件A的第X行中创建了一个问题,如果文件B在其第X行中出现问题,则不会创建此问题。

@Rule(key = "foo_key", name = "Foo issue", description = "Foo issue", priority = Priority.INFO)
public class FooCheck extends IssuableSubscriptionVisitor {

    private static final Map<String, Collection<Integer>> linesWithIssuesByClass = new HashMap<>();

    @Override
    public List<Kind> nodesToVisit() {
        return ImmutableList.of(Kind.NEW_CLASS);
    }

    @Override
    public void visitNode(Tree tree) {
        if (lineAlreadyHasThisIssue(tree)) {
            return;
        }

        reportIssue(tree);
    }

    private boolean lineAlreadyHasThisIssue(Tree tree) {
        if (tree.firstToken() != null) {
            final String classname = getFullyQualifiedNameOfClassOf(tree);
            final int line = tree.firstToken().line();

            return linesWithIssuesByClass.containsKey(classname)
                    && linesWithIssuesByClass.get(classname).contains(line);
        }

        return false;
    }

    private void reportIssue(Tree tree) {
        if (tree.firstToken() != null) {
            final String classname = getFullyQualifiedNameOfClassOf(tree);
            final int line = tree.firstToken().line();

            if (!linesWithIssuesByClass.containsKey(classname)) {
                linesWithIssuesByClass.put(classname, new ArrayList<>());
            }

            linesWithIssuesByClass.get(classname).add(line);
        }

        reportIssue(tree, "New class!");
    }

    private String getFullyQualifiedNameOfClassOf(Tree tree) {
        Tree parent = tree.parent();

        while (parent != null) {
            final Tree grandparent = parent.parent();

            if (parent.is(Kind.CLASS) && grandparent != null && grandparent.is(Kind.COMPILATION_UNIT)) {
                final String packageName = getPackageName((CompilationUnitTree) grandparent);

                return packageName.isEmpty()
                        ? getClassName((ClassTree) parent)
                        : packageName + '.' + getClassName((ClassTree) parent);
            }

            parent = parent.parent();
        }

        return "";
    }

    private String getPackageName(CompilationUnitTree compilationUnitTree) {
        final PackageDeclarationTree packageDeclarationTree = compilationUnitTree.packageDeclaration();
        if (packageDeclarationTree == null) {
            return "";
        }

        return packageDeclarationTree.packageName().toString();
    }

    private String getClassName(ClassTree classTree) {
        final IdentifierTree simpleName = classTree.simpleName();
        return simpleName == null
                ? ""
                : simpleName.toString();
    }

}

我检查tree.firstToken()是否为空,因为接口Tree具有以下代码:

@Nullable
SyntaxToken firstToken();

虽然Tree有另一个方法lastToken()返回另一个SyntaxToken行,但我们应该调用firstToken(),因为我们的节点可能是多行的,如下所示:

Foo foo = new Foo(
        new Bar()
);

lastToken().line()在每次访问节点时会有不同的值,而firstToken().line()则不会。