com.puppycrawl.tools.checkstyle.checks.whitespace.GenericWhitespaceCheck Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checkstyle Show documentation
Show all versions of checkstyle Show documentation
Checkstyle is a development tool to help programmers write Java code
that adheres to a coding standard
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 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.whitespace;
import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
/**
*
* Checks that the whitespace around the Generic tokens (angle brackets)
* "<" and ">" are correct to the typical convention.
* The convention is not configurable.
*
*
* Left angle bracket ("<"):
*
*
* - should be preceded with whitespace only
* in generic methods definitions.
* - should not be preceded with whitespace
* when it is precede method name or constructor.
* - should not be preceded with whitespace when following type name.
* - should not be followed with whitespace in all cases.
*
*
* Right angle bracket (">"):
*
*
* - should not be preceded with whitespace in all cases.
* - should be followed with whitespace in almost all cases,
* except diamond operators and when preceding method name or constructor.
*
* To configure the check:
*
*
* <module name="GenericWhitespace"/>
*
*
* Examples with correct spacing:
*
*
* // Generic methods definitions
* public void <K, V extends Number> boolean foo(K, V) {}
* // Generic type definition
* class name<T1, T2, ..., Tn> {}
* // Generic type reference
* OrderedPair<String, Box<Integer>> p;
* // Generic preceded method name
* boolean same = Util.<Integer, String>compare(p1, p2);
* // Diamond operator
* Pair<Integer, String> p1 = new Pair<>(1, "apple");
* // Method reference
* List<T> list = ImmutableList.Builder<T>::new;
* // Method reference
* sort(list, Comparable::<String>compareTo);
* // Constructor call
* MyClass obj = new <String>MyClass();
*
*
* Examples with incorrect spacing:
*
*
* List< String> l; // violation, "<" followed by whitespace
* Box b = Box. <String>of("foo"); // violation, "<" preceded with whitespace
* public<T> void foo() {} // violation, "<" not preceded with whitespace
*
* List a = new ArrayList<> (); // violation, ">" followed by whitespace
* Map<Integer, String>m; // violation, ">" not followed by whitespace
* Pair<Integer, Integer > p; // violation, ">" preceded with whitespace
*
*
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
*
*
* Violation Message Keys:
*
*
* -
* {@code ws.followed}
*
* -
* {@code ws.illegalFollow}
*
* -
* {@code ws.notPreceded}
*
* -
* {@code ws.preceded}
*
*
*
* @since 5.0
*/
@FileStatefulCheck
public class GenericWhitespaceCheck extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_WS_PRECEDED = "ws.preceded";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_WS_FOLLOWED = "ws.followed";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
/** Open angle bracket literal. */
private static final String OPEN_ANGLE_BRACKET = "<";
/** Close angle bracket literal. */
private static final String CLOSE_ANGLE_BRACKET = ">";
/** Used to count the depth of a Generic expression. */
private int depth;
@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
}
@Override
public int[] getAcceptableTokens() {
return getRequiredTokens();
}
@Override
public int[] getRequiredTokens() {
return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
}
@Override
public void beginTree(DetailAST rootAST) {
// Reset for each tree, just increase there are violations in preceding
// trees.
depth = 0;
}
@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.GENERIC_START:
processStart(ast);
depth++;
break;
case TokenTypes.GENERIC_END:
processEnd(ast);
depth--;
break;
default:
throw new IllegalArgumentException("Unknown type " + ast);
}
}
/**
* Checks the token for the end of Generics.
*
* @param ast the token to check
*/
private void processEnd(DetailAST ast) {
final String line = getLine(ast.getLineNo() - 1);
final int before = ast.getColumnNo() - 1;
final int after = ast.getColumnNo() + 1;
if (before >= 0 && Character.isWhitespace(line.charAt(before))
&& !containsWhitespaceBefore(before, line)) {
log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
}
if (after < line.length()) {
// Check if the last Generic, in which case must be a whitespace
// or a '(),[.'.
if (depth == 1) {
processSingleGeneric(ast, line, after);
}
else {
processNestedGenerics(ast, line, after);
}
}
}
/**
* Process Nested generics.
*
* @param ast token
* @param line line content
* @param after position after
*/
private void processNestedGenerics(DetailAST ast, String line, int after) {
// In a nested Generic type, so can only be a '>' or ',' or '&'
// In case of several extends definitions:
//
// class IntEnumValueType & IntEnum>
// ^
// should be whitespace if followed by & -+
//
final int indexOfAmp = line.indexOf('&', after);
if (indexOfAmp >= 1
&& containsWhitespaceBetween(after, indexOfAmp, line)) {
if (indexOfAmp - after == 0) {
log(ast, MSG_WS_NOT_PRECEDED, "&");
}
else if (indexOfAmp - after != 1) {
log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
}
}
else if (line.charAt(after) == ' ') {
log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
}
}
/**
* Process Single-generic.
*
* @param ast token
* @param line line content
* @param after position after
*/
private void processSingleGeneric(DetailAST ast, String line, int after) {
final char charAfter = line.charAt(after);
if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) {
if (Character.isWhitespace(charAfter)) {
log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
}
}
else if (!isCharacterValidAfterGenericEnd(charAfter)) {
log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
}
}
/**
* Checks if generic is before constructor invocation.
*
* @param ast ast
* @return true if generic before a constructor invocation
*/
private static boolean isGenericBeforeCtor(DetailAST ast) {
final DetailAST parent = ast.getParent();
return parent.getParent().getType() == TokenTypes.LITERAL_NEW
&& (parent.getNextSibling().getType() == TokenTypes.IDENT
|| parent.getNextSibling().getType() == TokenTypes.DOT);
}
/**
* Is generic before method reference.
*
* @param ast ast
* @return true if generic before a method ref
*/
private static boolean isGenericBeforeMethod(DetailAST ast) {
return ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
|| isAfterMethodReference(ast);
}
/**
* Checks if current generic end ('>') is located after
* {@link TokenTypes#METHOD_REF method reference operator}.
*
* @param genericEnd {@link TokenTypes#GENERIC_END}
* @return true if '>' follows after method reference.
*/
private static boolean isAfterMethodReference(DetailAST genericEnd) {
return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
}
/**
* Checks the token for the start of Generics.
*
* @param ast the token to check
*/
private void processStart(DetailAST ast) {
final String line = getLine(ast.getLineNo() - 1);
final int before = ast.getColumnNo() - 1;
final int after = ast.getColumnNo() + 1;
// Need to handle two cases as in:
//
// public static Callable callable(Runnable task, T result)
// ^ ^
// ws reqd ---+ +--- whitespace NOT required
//
if (before >= 0) {
// Detect if the first case
final DetailAST parent = ast.getParent();
final DetailAST grandparent = parent.getParent();
if (grandparent.getType() == TokenTypes.CTOR_DEF
|| grandparent.getType() == TokenTypes.METHOD_DEF
|| isGenericBeforeCtor(ast)) {
// Require whitespace
if (!Character.isWhitespace(line.charAt(before))) {
log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
}
}
// Whitespace not required
else if (Character.isWhitespace(line.charAt(before))
&& !containsWhitespaceBefore(before, line)) {
log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
}
}
if (after < line.length()
&& Character.isWhitespace(line.charAt(after))) {
log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
}
}
/**
* Returns whether the specified string contains only whitespace between
* specified indices.
*
* @param fromIndex the index to start the search from. Inclusive
* @param toIndex the index to finish the search. Exclusive
* @param line the line to check
* @return whether there are only whitespaces (or nothing)
*/
private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, String line) {
boolean result = true;
for (int i = fromIndex; i < toIndex; i++) {
if (!Character.isWhitespace(line.charAt(i))) {
result = false;
break;
}
}
return result;
}
/**
* Returns whether the specified string contains only whitespace up to specified index.
*
* @param before the index to start the search from. Inclusive
* @param line the index to finish the search. Exclusive
* @return {@code true} if there are only whitespaces,
* false if there is nothing before or some other characters
*/
private static boolean containsWhitespaceBefore(int before, String line) {
return before != 0 && CommonUtil.hasWhitespaceBefore(before, line);
}
/**
* Checks whether given character is valid to be right after generic ends.
*
* @param charAfter character to check
* @return checks if given character is valid
*/
private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
return charAfter == '(' || charAfter == ')'
|| charAfter == ',' || charAfter == '['
|| charAfter == '.' || charAfter == ':'
|| charAfter == ';'
|| Character.isWhitespace(charAfter);
}
}