com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck Maven / Gradle / Ivy
Show all versions of checkstyle Show documentation
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.annotation;
import java.util.Locale;
import com.puppycrawl.tools.checkstyle.StatelessCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
*
* This check controls the style with the usage of annotations.
*
*
* Annotations have three element styles starting with the least verbose.
*
*
* -
* {@code ElementStyle.COMPACT_NO_ARRAY}
*
* -
* {@code ElementStyle.COMPACT}
*
* -
* {@code ElementStyle.EXPANDED}
*
*
*
* To not enforce an element style a {@code ElementStyle.IGNORE} type is provided.
* The desired style can be set through the {@code elementStyle} property.
*
*
* Using the {@code ElementStyle.EXPANDED} style is more verbose.
* The expanded version is sometimes referred to as "named parameters" in other languages.
*
*
* Using the {@code ElementStyle.COMPACT} style is less verbose.
* This style can only be used when there is an element called 'value' which is either
* the sole element or all other elements have default values.
*
*
* Using the {@code ElementStyle.COMPACT_NO_ARRAY} style is less verbose.
* It is similar to the {@code ElementStyle.COMPACT} style but single value arrays are flagged.
* With annotations a single value array does not need to be placed in an array initializer.
*
*
* The ending parenthesis are optional when using annotations with no elements.
* To always require ending parenthesis use the {@code ClosingParens.ALWAYS} type.
* To never have ending parenthesis use the {@code ClosingParens.NEVER} type.
* To not enforce a closing parenthesis preference a {@code ClosingParens.IGNORE} type is provided.
* Set this through the {@code closingParens} property.
*
*
* Annotations also allow you to specify arrays of elements in a standard format.
* As with normal arrays, a trailing comma is optional.
* To always require a trailing comma use the {@code TrailingArrayComma.ALWAYS} type.
* To never have a trailing comma use the {@code TrailingArrayComma.NEVER} type.
* To not enforce a trailing array comma preference a {@code TrailingArrayComma.IGNORE} type
* is provided. Set this through the {@code trailingArrayComma} property.
*
*
* By default the {@code ElementStyle} is set to {@code COMPACT_NO_ARRAY},
* the {@code TrailingArrayComma} is set to {@code NEVER},
* and the {@code ClosingParens} is set to {@code NEVER}.
*
*
* According to the JLS, it is legal to include a trailing comma
* in arrays used in annotations but Sun's Java 5 & 6 compilers will not
* compile with this syntax. This may in be a bug in Sun's compilers
* since eclipse 3.4's built-in compiler does allow this syntax as
* defined in the JLS. Note: this was tested with compilers included with
* JDK versions 1.5.0.17 and 1.6.0.11 and the compiler included with eclipse 3.4.1.
*
*
* See
* Java Language specification, §9.7.
*
*
* -
* Property {@code elementStyle} - Define the annotation element styles.
* Default value is {@code compact_no_array}.
*
* -
* Property {@code closingParens} - Define the policy for ending parenthesis.
* Default value is {@code never}.
*
* -
* Property {@code trailingArrayComma} - Define the policy for trailing comma in arrays.
* Default value is {@code never}.
*
*
*
* To configure the check:
*
*
* <module name="AnnotationUseStyle"/>
*
*
* To configure the check to enforce an {@code expanded} style,
* with a trailing array comma set to {@code never}
* and always including the closing parenthesis.
*
*
* <module name="AnnotationUseStyle">
* <property name="elementStyle" value="expanded"/>
* <property name="trailingArrayComma" value="never"/>
* <property name="closingParens" value="always"/>
* </module>
*
*
* @since 5.0
*
*/
@StatelessCheck
public final class AnnotationUseStyleCheck extends AbstractCheck {
/**
* Defines the styles for defining elements in an annotation.
*/
public enum ElementStyle {
/**
* Expanded example
*
* @SuppressWarnings(value={"unchecked","unused",})
.
*/
EXPANDED,
/**
* Compact example
*
* @SuppressWarnings({"unchecked","unused",})
*
or
* @SuppressWarnings("unchecked")
.
*/
COMPACT,
/**
* Compact example
*
* @SuppressWarnings("unchecked")
.
*/
COMPACT_NO_ARRAY,
/**
* Mixed styles.
*/
IGNORE,
}
/**
* Defines the two styles for defining
* elements in an annotation.
*
*/
public enum TrailingArrayComma {
/**
* With comma example
*
* @SuppressWarnings(value={"unchecked","unused",})
.
*/
ALWAYS,
/**
* Without comma example
*
* @SuppressWarnings(value={"unchecked","unused"})
.
*/
NEVER,
/**
* Mixed styles.
*/
IGNORE,
}
/**
* Defines the two styles for defining
* elements in an annotation.
*
*/
public enum ClosingParens {
/**
* With parens example
*
* @Deprecated()
.
*/
ALWAYS,
/**
* Without parens example
*
* @Deprecated
.
*/
NEVER,
/**
* Mixed styles.
*/
IGNORE,
}
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_ANNOTATION_INCORRECT_STYLE =
"annotation.incorrect.style";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_ANNOTATION_PARENS_MISSING =
"annotation.parens.missing";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_ANNOTATION_PARENS_PRESENT =
"annotation.parens.present";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING =
"annotation.trailing.comma.missing";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT =
"annotation.trailing.comma.present";
/**
* The element name used to receive special linguistic support
* for annotation use.
*/
private static final String ANNOTATION_ELEMENT_SINGLE_NAME =
"value";
/**
* Define the annotation element styles.
*/
private ElementStyle elementStyle = ElementStyle.COMPACT_NO_ARRAY;
//defaulting to NEVER because of the strange compiler behavior
/**
* Define the policy for trailing comma in arrays.
*/
private TrailingArrayComma trailingArrayComma = TrailingArrayComma.NEVER;
/**
* Define the policy for ending parenthesis.
*/
private ClosingParens closingParens = ClosingParens.NEVER;
/**
* Setter to define the annotation element styles.
*
* @param style string representation
*/
public void setElementStyle(final String style) {
elementStyle = getOption(ElementStyle.class, style);
}
/**
* Setter to define the policy for trailing comma in arrays.
*
* @param comma string representation
*/
public void setTrailingArrayComma(final String comma) {
trailingArrayComma = getOption(TrailingArrayComma.class, comma);
}
/**
* Setter to define the policy for ending parenthesis.
*
* @param parens string representation
*/
public void setClosingParens(final String parens) {
closingParens = getOption(ClosingParens.class, parens);
}
/**
* Retrieves an {@link Enum Enum} type from a @{link String String}.
* @param the enum type
* @param enumClass the enum class
* @param value the string representing the enum
* @return the enum type
*/
private static > T getOption(final Class enumClass,
final String value) {
try {
return Enum.valueOf(enumClass, value.trim().toUpperCase(Locale.ENGLISH));
}
catch (final IllegalArgumentException iae) {
throw new IllegalArgumentException("unable to parse " + value, iae);
}
}
@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
}
@Override
public int[] getRequiredTokens() {
return new int[] {
TokenTypes.ANNOTATION,
};
}
@Override
public int[] getAcceptableTokens() {
return getRequiredTokens();
}
@Override
public void visitToken(final DetailAST ast) {
checkStyleType(ast);
checkCheckClosingParens(ast);
checkTrailingComma(ast);
}
/**
* Checks to see if the
* {@link ElementStyle AnnotationElementStyle}
* is correct.
*
* @param annotation the annotation token
*/
private void checkStyleType(final DetailAST annotation) {
switch (elementStyle) {
case COMPACT_NO_ARRAY:
checkCompactNoArrayStyle(annotation);
break;
case COMPACT:
checkCompactStyle(annotation);
break;
case EXPANDED:
checkExpandedStyle(annotation);
break;
case IGNORE:
default:
break;
}
}
/**
* Checks for expanded style type violations.
*
* @param annotation the annotation token
*/
private void checkExpandedStyle(final DetailAST annotation) {
final int valuePairCount =
annotation.getChildCount(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
if (valuePairCount == 0 && hasArguments(annotation)) {
log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE, ElementStyle.EXPANDED);
}
}
/**
* Checks that annotation has arguments.
*
* @param annotation to check
* @return true if annotation has arguments, false otherwise
*/
private static boolean hasArguments(DetailAST annotation) {
final DetailAST firstToken = annotation.findFirstToken(TokenTypes.LPAREN);
return firstToken != null && firstToken.getNextSibling().getType() != TokenTypes.RPAREN;
}
/**
* Checks for compact style type violations.
*
* @param annotation the annotation token
*/
private void checkCompactStyle(final DetailAST annotation) {
final int valuePairCount =
annotation.getChildCount(
TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
final DetailAST valuePair =
annotation.findFirstToken(
TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
if (valuePairCount == 1
&& ANNOTATION_ELEMENT_SINGLE_NAME.equals(
valuePair.getFirstChild().getText())) {
log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE,
ElementStyle.COMPACT);
}
}
/**
* Checks for compact no array style type violations.
*
* @param annotation the annotation token
*/
private void checkCompactNoArrayStyle(final DetailAST annotation) {
final DetailAST arrayInit =
annotation.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
//in compact style with one value
if (arrayInit != null
&& arrayInit.getChildCount(TokenTypes.EXPR) == 1) {
log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE,
ElementStyle.COMPACT_NO_ARRAY);
}
//in expanded style with pairs
else {
DetailAST ast = annotation.getFirstChild();
while (ast != null) {
final DetailAST nestedArrayInit =
ast.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
if (nestedArrayInit != null
&& nestedArrayInit.getChildCount(TokenTypes.EXPR) == 1) {
log(annotation.getLineNo(), MSG_KEY_ANNOTATION_INCORRECT_STYLE,
ElementStyle.COMPACT_NO_ARRAY);
}
ast = ast.getNextSibling();
}
}
}
/**
* Checks to see if the trailing comma is present if required or
* prohibited.
*
* @param annotation the annotation token
*/
private void checkTrailingComma(final DetailAST annotation) {
if (trailingArrayComma != TrailingArrayComma.IGNORE) {
DetailAST child = annotation.getFirstChild();
while (child != null) {
DetailAST arrayInit = null;
if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
arrayInit = child.findFirstToken(TokenTypes.ANNOTATION_ARRAY_INIT);
}
else if (child.getType() == TokenTypes.ANNOTATION_ARRAY_INIT) {
arrayInit = child;
}
if (arrayInit != null) {
logCommaViolation(arrayInit);
}
child = child.getNextSibling();
}
}
}
/**
* Logs a trailing array comma violation if one exists.
*
* @param ast the array init
* {@link TokenTypes#ANNOTATION_ARRAY_INIT ANNOTATION_ARRAY_INIT}.
*/
private void logCommaViolation(final DetailAST ast) {
final DetailAST rCurly = ast.findFirstToken(TokenTypes.RCURLY);
//comma can be null if array is empty
final DetailAST comma = rCurly.getPreviousSibling();
if (trailingArrayComma == TrailingArrayComma.ALWAYS) {
if (comma == null || comma.getType() != TokenTypes.COMMA) {
log(rCurly, MSG_KEY_ANNOTATION_TRAILING_COMMA_MISSING);
}
}
else if (comma != null && comma.getType() == TokenTypes.COMMA) {
log(comma, MSG_KEY_ANNOTATION_TRAILING_COMMA_PRESENT);
}
}
/**
* Checks to see if the closing parenthesis are present if required or
* prohibited.
*
* @param ast the annotation token
*/
private void checkCheckClosingParens(final DetailAST ast) {
if (closingParens != ClosingParens.IGNORE) {
final DetailAST paren = ast.getLastChild();
if (closingParens == ClosingParens.ALWAYS) {
if (paren.getType() != TokenTypes.RPAREN) {
log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_MISSING);
}
}
else if (paren.getPreviousSibling().getType() == TokenTypes.LPAREN) {
log(ast.getLineNo(), MSG_KEY_ANNOTATION_PARENS_PRESENT);
}
}
}
}