Coverage Report - com.puppycrawl.tools.checkstyle.checks.whitespace.GenericWhitespaceCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
GenericWhitespaceCheck
100%
75/75
100%
81/81
3.714
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.whitespace;
 21  
 
 22  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 23  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 27  
 
 28  
 /**
 29  
  * <p>
 30  
  * Checks that the whitespace around the Generic tokens (angle brackets)
 31  
  * "&lt;" and "&gt;" are correct to the <i>typical</i> convention.
 32  
  * The convention is not configurable.
 33  
  * </p>
 34  
  * <br>
 35  
  * <p>
 36  
  * Left angle bracket ("&lt;"):
 37  
  * </p>
 38  
  * <br>
 39  
  * <ul>
 40  
  * <li> should be preceded with whitespace only
 41  
  *   in generic methods definitions.</li>
 42  
  * <li> should not be preceded with whitespace
 43  
  *   when it is precede method name or following type name.</li>
 44  
  * <li> should not be followed with whitespace in all cases.</li>
 45  
  * </ul>
 46  
  * <br>
 47  
  * <p>
 48  
  * Right angle bracket ("&gt;"):
 49  
  * </p>
 50  
  * <br>
 51  
  * <ul>
 52  
  * <li> should not be preceded with whitespace in all cases.</li>
 53  
  * <li> should be followed with whitespace in almost all cases,
 54  
  *   except diamond operators and when preceding method name.</li></ul>
 55  
  * <br>
 56  
  * <p>
 57  
  * Examples with correct spacing:
 58  
  * </p>
 59  
  * <br>
 60  
  * <pre>
 61  
  * public void &lt;K, V extends Number&gt; boolean foo(K, V) {}  // Generic methods definitions
 62  
  * class name&lt;T1, T2, ..., Tn&gt; {}                          // Generic type definition
 63  
  * OrderedPair&lt;String, Box&lt;Integer&gt;&gt; p;              // Generic type reference
 64  
  * boolean same = Util.&lt;Integer, String&gt;compare(p1, p2);   // Generic preceded method name
 65  
  * Pair&lt;Integer, String&gt; p1 = new Pair&lt;&gt;(1, "apple");// Diamond operator
 66  
  * List&lt;T&gt; list = ImmutableList.Builder&lt;T&gt;::new;     // Method reference
 67  
  * sort(list, Comparable::&lt;String&gt;compareTo);              // Method reference
 68  
  * </pre>
 69  
  * @author Oliver Burn
 70  
  */
 71  
 @FileStatefulCheck
 72  20
 public class GenericWhitespaceCheck extends AbstractCheck {
 73  
 
 74  
     /**
 75  
      * A key is pointing to the warning message text in "messages.properties"
 76  
      * file.
 77  
      */
 78  
     public static final String MSG_WS_PRECEDED = "ws.preceded";
 79  
 
 80  
     /**
 81  
      * A key is pointing to the warning message text in "messages.properties"
 82  
      * file.
 83  
      */
 84  
     public static final String MSG_WS_FOLLOWED = "ws.followed";
 85  
 
 86  
     /**
 87  
      * A key is pointing to the warning message text in "messages.properties"
 88  
      * file.
 89  
      */
 90  
     public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded";
 91  
 
 92  
     /**
 93  
      * A key is pointing to the warning message text in "messages.properties"
 94  
      * file.
 95  
      */
 96  
     public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow";
 97  
 
 98  
     /** Open angle bracket literal. */
 99  
     private static final String OPEN_ANGLE_BRACKET = "<";
 100  
 
 101  
     /** Close angle bracket literal. */
 102  
     private static final String CLOSE_ANGLE_BRACKET = ">";
 103  
 
 104  
     /** Used to count the depth of a Generic expression. */
 105  
     private int depth;
 106  
 
 107  
     @Override
 108  
     public int[] getDefaultTokens() {
 109  27
         return getRequiredTokens();
 110  
     }
 111  
 
 112  
     @Override
 113  
     public int[] getAcceptableTokens() {
 114  6
         return getRequiredTokens();
 115  
     }
 116  
 
 117  
     @Override
 118  
     public int[] getRequiredTokens() {
 119  61
         return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END};
 120  
     }
 121  
 
 122  
     @Override
 123  
     public void beginTree(DetailAST rootAST) {
 124  
         // Reset for each tree, just increase there are errors in preceding
 125  
         // trees.
 126  10
         depth = 0;
 127  10
     }
 128  
 
 129  
     @Override
 130  
     public void visitToken(DetailAST ast) {
 131  167
         switch (ast.getType()) {
 132  
             case TokenTypes.GENERIC_START:
 133  83
                 processStart(ast);
 134  83
                 depth++;
 135  83
                 break;
 136  
             case TokenTypes.GENERIC_END:
 137  83
                 processEnd(ast);
 138  83
                 depth--;
 139  83
                 break;
 140  
             default:
 141  1
                 throw new IllegalArgumentException("Unknown type " + ast);
 142  
         }
 143  166
     }
 144  
 
 145  
     /**
 146  
      * Checks the token for the end of Generics.
 147  
      * @param ast the token to check
 148  
      */
 149  
     private void processEnd(DetailAST ast) {
 150  83
         final String line = getLine(ast.getLineNo() - 1);
 151  83
         final int before = ast.getColumnNo() - 1;
 152  83
         final int after = ast.getColumnNo() + 1;
 153  
 
 154  83
         if (before >= 0 && Character.isWhitespace(line.charAt(before))
 155  8
                 && !containsWhitespaceBefore(before, line)) {
 156  7
             log(ast.getLineNo(), before, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET);
 157  
         }
 158  
 
 159  83
         if (after < line.length()) {
 160  
 
 161  
             // Check if the last Generic, in which case must be a whitespace
 162  
             // or a '(),[.'.
 163  82
             if (depth == 1) {
 164  58
                 processSingleGeneric(ast, line, after);
 165  
             }
 166  
             else {
 167  24
                 processNestedGenerics(ast, line, after);
 168  
             }
 169  
         }
 170  83
     }
 171  
 
 172  
     /**
 173  
      * Process Nested generics.
 174  
      * @param ast token
 175  
      * @param line line content
 176  
      * @param after position after
 177  
      */
 178  
     private void processNestedGenerics(DetailAST ast, String line, int after) {
 179  
         // In a nested Generic type, so can only be a '>' or ',' or '&'
 180  
 
 181  
         // In case of several extends definitions:
 182  
         //
 183  
         //   class IntEnumValueType<E extends Enum<E> & IntEnum>
 184  
         //                                          ^
 185  
         //   should be whitespace if followed by & -+
 186  
         //
 187  24
         final int indexOfAmp = line.indexOf('&', after);
 188  24
         if (indexOfAmp >= 1
 189  6
             && containsWhitespaceBetween(after, indexOfAmp, line)) {
 190  5
             if (indexOfAmp - after == 0) {
 191  2
                 log(ast.getLineNo(), after, MSG_WS_NOT_PRECEDED, "&");
 192  
             }
 193  3
             else if (indexOfAmp - after != 1) {
 194  1
                 log(ast.getLineNo(), after, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
 195  
             }
 196  
         }
 197  19
         else if (line.charAt(after) == ' ') {
 198  2
             log(ast.getLineNo(), after, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
 199  
         }
 200  24
     }
 201  
 
 202  
     /**
 203  
      * Process Single-generic.
 204  
      * @param ast token
 205  
      * @param line line content
 206  
      * @param after position after
 207  
      */
 208  
     private void processSingleGeneric(DetailAST ast, String line, int after) {
 209  58
         final char charAfter = line.charAt(after);
 210  
 
 211  
         // Need to handle a number of cases. First is:
 212  
         //    Collections.<Object>emptySet();
 213  
         //                        ^
 214  
         //                        +--- whitespace not allowed
 215  58
         if (isGenericBeforeMethod(ast)) {
 216  7
             if (Character.isWhitespace(charAfter)) {
 217  2
                 log(ast.getLineNo(), after, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET);
 218  
             }
 219  
         }
 220  51
         else if (!isCharacterValidAfterGenericEnd(charAfter)) {
 221  1
             log(ast.getLineNo(), after, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET);
 222  
         }
 223  58
     }
 224  
 
 225  
     /**
 226  
      * Is generic before method reference.
 227  
      * @param ast ast
 228  
      * @return true if generic before a method ref
 229  
      */
 230  
     private static boolean isGenericBeforeMethod(DetailAST ast) {
 231  116
         return ast.getParent().getType() == TokenTypes.TYPE_ARGUMENTS
 232  45
                 && ast.getParent().getParent().getType() == TokenTypes.DOT
 233  6
                 && ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL
 234  54
                 || isAfterMethodReference(ast);
 235  
     }
 236  
 
 237  
     /**
 238  
      * Checks if current generic end ('>') is located after
 239  
      * {@link TokenTypes#METHOD_REF method reference operator}.
 240  
      * @param genericEnd {@link TokenTypes#GENERIC_END}
 241  
      * @return true if '>' follows after method reference.
 242  
      */
 243  
     private static boolean isAfterMethodReference(DetailAST genericEnd) {
 244  54
         return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF;
 245  
     }
 246  
 
 247  
     /**
 248  
      * Checks the token for the start of Generics.
 249  
      * @param ast the token to check
 250  
      */
 251  
     private void processStart(DetailAST ast) {
 252  83
         final String line = getLine(ast.getLineNo() - 1);
 253  83
         final int before = ast.getColumnNo() - 1;
 254  83
         final int after = ast.getColumnNo() + 1;
 255  
 
 256  
         // Need to handle two cases as in:
 257  
         //
 258  
         //   public static <T> Callable<T> callable(Runnable task, T result)
 259  
         //                 ^           ^
 260  
         //      ws reqd ---+           +--- whitespace NOT required
 261  
         //
 262  83
         if (before >= 0) {
 263  
             // Detect if the first case
 264  82
             final DetailAST parent = ast.getParent();
 265  82
             final DetailAST grandparent = parent.getParent();
 266  82
             if (parent.getType() == TokenTypes.TYPE_PARAMETERS
 267  14
                 && (grandparent.getType() == TokenTypes.CTOR_DEF
 268  12
                     || grandparent.getType() == TokenTypes.METHOD_DEF)) {
 269  
                 // Require whitespace
 270  5
                 if (!Character.isWhitespace(line.charAt(before))) {
 271  1
                     log(ast.getLineNo(), before, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET);
 272  
                 }
 273  
             }
 274  
             // Whitespace not required
 275  77
             else if (Character.isWhitespace(line.charAt(before))
 276  9
                 && !containsWhitespaceBefore(before, line)) {
 277  8
                 log(ast.getLineNo(), before, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET);
 278  
             }
 279  
         }
 280  
 
 281  83
         if (after < line.length()
 282  82
                 && Character.isWhitespace(line.charAt(after))) {
 283  6
             log(ast.getLineNo(), after, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET);
 284  
         }
 285  83
     }
 286  
 
 287  
     /**
 288  
      * Returns whether the specified string contains only whitespace between
 289  
      * specified indices.
 290  
      *
 291  
      * @param fromIndex the index to start the search from. Inclusive
 292  
      * @param toIndex the index to finish the search. Exclusive
 293  
      * @param line the line to check
 294  
      * @return whether there are only whitespaces (or nothing)
 295  
      */
 296  
     private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, String line) {
 297  6
         boolean result = true;
 298  10
         for (int i = fromIndex; i < toIndex; i++) {
 299  5
             if (!Character.isWhitespace(line.charAt(i))) {
 300  1
                 result = false;
 301  1
                 break;
 302  
             }
 303  
         }
 304  6
         return result;
 305  
     }
 306  
 
 307  
     /**
 308  
      * Returns whether the specified string contains only whitespace up to specified index.
 309  
      *
 310  
      * @param before the index to start the search from. Inclusive
 311  
      * @param line   the index to finish the search. Exclusive
 312  
      * @return {@code true} if there are only whitespaces,
 313  
      *     false if there is nothing before or some other characters
 314  
      */
 315  
     private static boolean containsWhitespaceBefore(int before, String line) {
 316  17
         return before != 0 && CommonUtils.hasWhitespaceBefore(before, line);
 317  
     }
 318  
 
 319  
     /**
 320  
      * Checks whether given character is valid to be right after generic ends.
 321  
      * @param charAfter character to check
 322  
      * @return checks if given character is valid
 323  
      */
 324  
     private static boolean isCharacterValidAfterGenericEnd(char charAfter) {
 325  102
         return charAfter == '(' || charAfter == ')'
 326  
             || charAfter == ',' || charAfter == '['
 327  
             || charAfter == '.' || charAfter == ':'
 328  
             || charAfter == ';'
 329  37
             || Character.isWhitespace(charAfter);
 330  
     }
 331  
 }