【问题标题】:Checkstyle rule for "@annotations must be on separate line"“@annotations 必须在单独的行上”的检查样式规则
【发布时间】:2012-05-25 01:54:39
【问题描述】:

我正在尝试为 checkstyle 创建一个规则,这将阻止编写内联注释用法,如下所示:

@Entity MyClass someEntity;
@Foo(a="B") public void bar(Baz baz) {
}

但不会阻止这样的想法:

public void bar(@Param Baz baz) {
}

有什么方法可以实现吗?

【问题讨论】:

  • Checkstyle 似乎有一个相当灵活的 API,所以我想这是可能的。你试过什么?
  • 我尝试创建正则表达式,但写不正确
  • 你能在你的问题中写下你的正则表达式,以便我们查明问题所在吗?
  • 我觉得很奇怪你想阻止方法参数的内联注释。对我来说,这似乎是最干净的方式。
  • 我不确定是否可以使用正则表达式。它们可能不足以验证@Foo(a=" \") public void bar(Baz baz) {") 之类的内容。您可能需要 AST 访问者。我也同意这条规则不是一个好主意,但如果你真的必须这样做,试着做一些弱一点的事情。只是假设注释参数中没有括号。然后看起来很容易

标签: java annotations checkstyle


【解决方案1】:

这个答案的大部分灵感来自Checkstyle's "Writing Checks" article。大部分工作在AnnotationSameLineCheck完成。

AnnotationSameLineCheck.java

此 Java 文件的灵感来自“Writing Checks”文章中的"Visitor In Action" section

getDefaultTokens 定义了我们感兴趣的 Java 文件的哪些部分(也称为标记)。第一个可能会认为我们会对 TokenTypes.ANNOTATION 感兴趣,但事实并非如此。我们对TokenTypes.ANNOTATION 不感兴趣,因为我们不想检查所有注释;我们实际上想忽略TokenTypes.PARAMETER_DEF

那么我们对什么感兴趣?我们实际上对可以注释的 Java 文件的那些部分感兴趣(即类定义TokenTypes.CLASS_DEF、方法定义TokenTypes.METHOD_DEF 等)。方便的是,Checkstyle 的 API 有一个方法可以告诉我们标记是否被注释。那个方法是AnnotationUtility.containsAnnotation,用在visitToken中。

用于确定注解是否与 Java 文件的其他部分在同一行的算法如下:

  1. 确定特定标记是否包含注释。如果没有,什么也不做。
  2. 找到注释标记。
  3. 在注释标记之前找到标记。
  4. 在注解标记之后找到标记。
  5. 如果注释标记与前一个标记在同一行,则记录错误。
  6. 如果注释标记与下一个标记在同一行,则记录错误。

这个算法可以在visitToken找到。

package example;

import com.puppycrawl.tools.checkstyle.api.AnnotationUtility;
import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

public class AnnotationSameLineCheck extends Check {
    @Override
    public int[] getDefaultTokens() {
        // PACKAGE_DEF and PARAMETER_DEF were left out of the list
        return new int[] { TokenTypes.ANNOTATION_DEF, //
                TokenTypes.ANNOTATION_FIELD_DEF, //
                TokenTypes.CLASS_DEF, //
                TokenTypes.CTOR_DEF, //
                TokenTypes.ENUM_DEF, //
                TokenTypes.ENUM_CONSTANT_DEF, //
                TokenTypes.INTERFACE_DEF, //
                TokenTypes.METHOD_DEF, //
                TokenTypes.VARIABLE_DEF };
    }

    @Override
    public void visitToken(DetailAST ast) {
        if (AnnotationUtility.containsAnnotation(ast)) {
            final DetailAST holder = AnnotationUtility.getAnnotationHolder(ast);
            final DetailAST annotation = getAnnotationAst(holder);
            final DetailAST prev = getPreviousSibling(annotation, holder, ast);
            final DetailAST next = getNextSibling(annotation, holder, ast);
            if (isPreviousSiblingOnSameLine(prev, annotation) || //
                    isNextSiblingOnSameLine(annotation, next)) {
                log(annotation.getLineNo(), //
                        annotation.getColumnNo(), //
                        "Annotations must exist on their own line");
            }
        }
    }

    private static boolean isPreviousSiblingOnSameLine(DetailAST prev, DetailAST annotation) {
        if (prev == null) {
            return false;
        } else if (prev.getLastChild() == null) {
            return prev.getLineNo() == annotation.getLineNo();
        }
        return prev.getLastChild().getLineNo() == annotation.getLineNo();
    }

    private static boolean isNextSiblingOnSameLine(DetailAST annotation, DetailAST next) {
        if (next == null) {
            return false;
        }
        return annotation.getLineNo() == next.getLineNo();
    }

    private static DetailAST getAnnotationAst(DetailAST aHolderAst) {
        if (aHolderAst.getType() == TokenTypes.ANNOTATIONS) {
            return aHolderAst;
        } else if (aHolderAst.getType() == TokenTypes.MODIFIERS) {
            return aHolderAst.findFirstToken(TokenTypes.ANNOTATION);
        }
        throw new AssertionError("aHolder must be one of TokenTypes.ANNOTATIONS or TokenTypes.MODIFIERS but was " + aHolderAst);
    }

    private static DetailAST getPreviousSibling(DetailAST annotation, DetailAST holder, DetailAST ast) {
        if (annotation.getPreviousSibling() != null) {
            return annotation.getPreviousSibling();
        } else if (holder.getPreviousSibling() != null) {
            return holder.getPreviousSibling();
        }
        return ast.getPreviousSibling();
    }

    private static DetailAST getNextSibling(DetailAST annotation, DetailAST holder, DetailAST ast) {
        if (annotation.getNextSibling() != null) {
            return annotation.getNextSibling();
        } else if (holder.getNextSibling() != null) {
            return holder.getNextSibling();
        }
        return ast.getNextSibling();
    }
}

checks.xml

这个 XML 文件的灵感来自于“Writing Checks”一文的"Integrate Your Check" section。 Checkstyle 使用它来指定对一组 Java 文件执行哪些检查。请注意,AnnotationSameLineCheck 是完全限定的(即,它的包在名称中指定)。 checks.xmlbuild.xml 文件中被提及。

<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
          "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
          "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
    <module name="TreeWalker">
        <module name="example.AnnotationSameLineCheck"/>
    </module>
</module>

build.xml

这个 Ant 构建文件的灵感来自 Checkstyle's "Ant Task" article。使用 Ant 只是执行 Checkstyle 的一种方式。使用命令行是另一种选择。

<project default="example">
    <taskdef resource="checkstyletask.properties" classpath="target/classes:lib/checkstyle-5.5-all.jar" />
    <target name="example">
        <checkstyle config="checks.xml">
            <fileset dir="src/main/java" includes="**/AnnotatedClass.java" />
            <formatter type="plain" />
        </checkstyle>
    </target>
</project>

AnnotationClass.java

可以使用下面的类来测试AnnotationSameLineCheckbuild.xml 文件中提到了它。注意接口、类、枚举、成员变量、方法、参数和局部变量声明的测试。

package example;
    @Deprecated
class CorrectClassDefA {}

@Deprecated class IncorrectClassDefA {}

abstract
@Deprecated
class CorrectClassDefB {}

abstract @SuppressWarnings(value = "unused") class IncorrectClassDefB0 {}

abstract
    @Deprecated class IncorrectClassDefB1 {}

abstract@Deprecated
class IncorrectClassDefB2 {}

@Deprecated abstract class IncorrectClassDefB3 {}

@Deprecated
abstract class CorrectClassDefB4 {}

@SuppressWarnings(value = "unused")
interface CorrectInterfaceDefA {}

@Deprecated interface IncorrectInterfaceDefA {}

abstract
@Deprecated
interface CorrectInterfaceDefB {}

abstract @Deprecated interface IncorrectInterfaceDefB0 {}

abstract
@Deprecated interface IncorrectInterfaceDefB1 {}

abstract @SuppressWarnings(value = "unused")
interface IncorrectInterfaceDefB2 {}

@SuppressWarnings(value = "unused") abstract interface IncorrectInterfaceDefB3 {}

@SuppressWarnings(value = "unused")
abstract 
interface CorrectInterfaceDefB4 {}

@Deprecated
enum CorrectEnumA {
    @SuppressWarnings(value = "unused")
    CORRECT,
    @Deprecated INCORRECT }

@Deprecated enum 
IncorrectEnumA {
@Deprecated
    CORRECT,
    @SuppressWarnings(value = "unused") INCORRECT }


public class AnnotatedClass { @Deprecated // incorrect
    public AnnotatedClass() {}

    @Deprecated
    AnnotatedClass(int correct) {}

    public
    @SuppressWarnings(value = "unused")
    AnnotatedClass(boolean correct, boolean correct0) {}

    @SuppressWarnings(value = "unused")
    AnnotatedClass(int correct, int correct0, int correct1) {}

    public @SuppressWarnings(value = "unused")
    AnnotatedClass(@Deprecated int bad, int bad0, int bad1, int bad2) {}

    @SuppressWarnings(value = "unused") AnnotatedClass(@Deprecated int bad, int bad0, int bad1, int bad2, int bad3) {}

    @Deprecated private int incorrectB;

    transient @Deprecated 
    private int incorrectC;

    transient
    @Deprecated 
    private 
    int correctD;

    private
    @SuppressWarnings(value = "unused")
    Object correctA; @SuppressWarnings(value = "dog")
     public void incorrectA(final Object baz) {
    }

    public void correctB(@SuppressWarnings(value = "dog") final Object good) {
        @Deprecated
        int correctA;

        final @Deprecated int incorrectB;

        final
        @Deprecated
        Object
        correctC;
    }

    @SuppressWarnings(value = "dog") public 
    void incorrectC(final Object bad) {
    }

    public
    @SuppressWarnings(value = "dog") void incorrectD(final Object bad) {
    }
}

【讨论】:

  • 非常令人印象深刻!感谢您的详细回答!
  • 我很乐意在我们的Checkstyle extension 中托管您的代码,请贡献
猜你喜欢
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-13
  • 2016-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多