Правило checkstyle для "@annotations должно быть в отдельной строке"

Я пытаюсь создать правило для checkstyle, что предотвратит использование встроенных аннотаций, например:

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

но не помешает так думать:

public void bar(@Param Baz baz) {
}

Есть ли способ достичь этого?

Ответ 1

Большая часть этого ответа была вдохновлена ​​проверкой "Написание чек" статьи. Большая часть работы выполняется в AnnotationSameLineCheck.

AnnotationSameLineCheck.java

Этот файл Java был вдохновлен "Посетителем в действии" статьи "Письменные проверки".

getDefaultTokens определяет, какие части (a.k.a. tokens) файла 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();
    }
}

checks.xml

Этот XML файл был вдохновлен секцией "Интеграция вашей проверки" статьи "Письменные проверки". Он используется 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>

build.xml

Этот файл сборки Ant был вдохновлен Checkstyle "Ant Задача" . Использование 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. Обратите внимание на тестирование объявлений интерфейса, класса, enum, member-variable, method, parameter и local-variable.

package example;
    @Deprecated
class CorrectClassDefA {}

@Deprecated class IncorrectClassDefA {}

abstract
@Deprecated
class CorrectClassDefB {}

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

abstract
    @Deprecated class IncorrectClassDefB1 {}

[email protected]
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) {
    }
}