我正在尝试为 checkstyle 创建一个规则,这将阻止编写内联注释用法,如下所示:
@Entity MyClass someEntity;
@Foo(a="B") public void bar(Baz baz) {
}
但不会阻止这样的想法:
public void bar(@Param Baz baz) {
}
有没有办法做到这一点?
我正在尝试为 checkstyle 创建一个规则,这将阻止编写内联注释用法,如下所示:
@Entity MyClass someEntity;
@Foo(a="B") public void bar(Baz baz) {
}
但不会阻止这样的想法:
public void bar(@Param Baz baz) {
}
有没有办法做到这一点?
这个答案的大部分灵感来自Checkstyle 的“Writing Checks”文章。大部分工作都是在AnnotationSameLineCheck
.
AnnotationSameLineCheck.java
这个 Java 文件的灵感来自于“Writing Checks”一文的“Visitor In Action”部分。
getDefaultTokens
定义我们感兴趣的 Java 文件的哪些部分(又名标记)。第一个可能会认为我们会感兴趣TokenTypes.ANNOTATION
,但事实并非如此。我们不感兴趣,TokenTypes.ANNOTATION
因为我们不想检查所有注释;我们其实想无视TokenTypes.PARAMETER_DEF
。
那我们对什么感兴趣?我们实际上对 Java 文件中可以注释的那些部分(即类定义TokenTypes.CLASS_DEF
、方法定义TokenTypes.METHOD_DEF
等)感兴趣。方便的是,Checkstyle 的 API 有一个方法可以告诉我们标记是否被注释。那个方法就是AnnotationUtility.containsAnnotation
,用在visitToken
。
用于确定注解是否与 Java 文件的其他部分位于同一行的算法如下:
该算法可在 中找到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();
}
}
检查.xml
此 XML 文件的灵感来自“Writing Checks”一文的“Integrate Your Check”部分。Checkstyle 使用它来指定对一组 Java 文件执行哪些检查。注意AnnotationSameLineCheck
是完全限定的(即,它的包是在名称中指定的)。checks.xml
文件中提到build.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>
构建.xml
这个 Ant 构建文件的灵感来自Checkstyle 的“Ant Task”文章。使用 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
可以使用下面的类来测试AnnotationSameLineCheck
。文件中提到了build.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) {
}
}