001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.whitespace; 021 022import java.util.stream.IntStream; 023 024import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.CodePointUtil; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030 031/** 032 * <p> 033 * Checks that the whitespace around the Generic tokens (angle brackets) 034 * "<" and ">" are correct to the <i>typical</i> convention. 035 * The convention is not configurable. 036 * </p> 037 * <p> 038 * Left angle bracket ("<"): 039 * </p> 040 * <ul> 041 * <li> should be preceded with whitespace only 042 * in generic methods definitions.</li> 043 * <li> should not be preceded with whitespace 044 * when it is precede method name or constructor.</li> 045 * <li> should not be preceded with whitespace when following type name.</li> 046 * <li> should not be followed with whitespace in all cases.</li> 047 * </ul> 048 * <p> 049 * Right angle bracket (">"): 050 * </p> 051 * <ul> 052 * <li> should not be preceded with whitespace in all cases.</li> 053 * <li> should be followed with whitespace in almost all cases, 054 * except diamond operators and when preceding method name or constructor.</li></ul> 055 * <p> 056 * To configure the check: 057 * </p> 058 * <pre> 059 * <module name="GenericWhitespace"/> 060 * </pre> 061 * <p> 062 * Examples with correct spacing: 063 * </p> 064 * <pre> 065 * // Generic methods definitions 066 * public void <K, V extends Number> boolean foo(K, V) {} 067 * // Generic type definition 068 * class name<T1, T2, ..., Tn> {} 069 * // Generic type reference 070 * OrderedPair<String, Box<Integer>> p; 071 * // Generic preceded method name 072 * boolean same = Util.<Integer, String>compare(p1, p2); 073 * // Diamond operator 074 * Pair<Integer, String> p1 = new Pair<>(1, "apple"); 075 * // Method reference 076 * List<T> list = ImmutableList.Builder<T>::new; 077 * // Method reference 078 * sort(list, Comparable::<String>compareTo); 079 * // Constructor call 080 * MyClass obj = new <String>MyClass(); 081 * </pre> 082 * <p> 083 * Examples with incorrect spacing: 084 * </p> 085 * <pre> 086 * List< String> l; // violation, "<" followed by whitespace 087 * Box b = Box. <String>of("foo"); // violation, "<" preceded with whitespace 088 * public<T> void foo() {} // violation, "<" not preceded with whitespace 089 * 090 * List a = new ArrayList<> (); // violation, ">" followed by whitespace 091 * Map<Integer, String>m; // violation, ">" not followed by whitespace 092 * Pair<Integer, Integer > p; // violation, ">" preceded with whitespace 093 * </pre> 094 * <p> 095 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 096 * </p> 097 * <p> 098 * Violation Message Keys: 099 * </p> 100 * <ul> 101 * <li> 102 * {@code ws.followed} 103 * </li> 104 * <li> 105 * {@code ws.illegalFollow} 106 * </li> 107 * <li> 108 * {@code ws.notPreceded} 109 * </li> 110 * <li> 111 * {@code ws.preceded} 112 * </li> 113 * </ul> 114 * 115 * @since 5.0 116 */ 117@FileStatefulCheck 118public class GenericWhitespaceCheck extends AbstractCheck { 119 120 /** 121 * A key is pointing to the warning message text in "messages.properties" 122 * file. 123 */ 124 public static final String MSG_WS_PRECEDED = "ws.preceded"; 125 126 /** 127 * A key is pointing to the warning message text in "messages.properties" 128 * file. 129 */ 130 public static final String MSG_WS_FOLLOWED = "ws.followed"; 131 132 /** 133 * A key is pointing to the warning message text in "messages.properties" 134 * file. 135 */ 136 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded"; 137 138 /** 139 * A key is pointing to the warning message text in "messages.properties" 140 * file. 141 */ 142 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow"; 143 144 /** Open angle bracket literal. */ 145 private static final String OPEN_ANGLE_BRACKET = "<"; 146 147 /** Close angle bracket literal. */ 148 private static final String CLOSE_ANGLE_BRACKET = ">"; 149 150 /** Used to count the depth of a Generic expression. */ 151 private int depth; 152 153 @Override 154 public int[] getDefaultTokens() { 155 return getRequiredTokens(); 156 } 157 158 @Override 159 public int[] getAcceptableTokens() { 160 return getRequiredTokens(); 161 } 162 163 @Override 164 public int[] getRequiredTokens() { 165 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END}; 166 } 167 168 @Override 169 public void beginTree(DetailAST rootAST) { 170 // Reset for each tree, just increase there are violations in preceding 171 // trees. 172 depth = 0; 173 } 174 175 @Override 176 public void visitToken(DetailAST ast) { 177 switch (ast.getType()) { 178 case TokenTypes.GENERIC_START: 179 processStart(ast); 180 depth++; 181 break; 182 case TokenTypes.GENERIC_END: 183 processEnd(ast); 184 depth--; 185 break; 186 default: 187 throw new IllegalArgumentException("Unknown type " + ast); 188 } 189 } 190 191 /** 192 * Checks the token for the end of Generics. 193 * 194 * @param ast the token to check 195 */ 196 private void processEnd(DetailAST ast) { 197 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 198 final int before = ast.getColumnNo() - 1; 199 final int after = ast.getColumnNo() + 1; 200 201 if (before >= 0 && CommonUtil.isCodePointWhitespace(line, before) 202 && !containsWhitespaceBefore(before, line)) { 203 log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET); 204 } 205 206 if (after < line.length) { 207 // Check if the last Generic, in which case must be a whitespace 208 // or a '(),[.'. 209 if (depth == 1) { 210 processSingleGeneric(ast, line, after); 211 } 212 else { 213 processNestedGenerics(ast, line, after); 214 } 215 } 216 } 217 218 /** 219 * Process Nested generics. 220 * 221 * @param ast token 222 * @param line unicode code points array of line 223 * @param after position after 224 */ 225 private void processNestedGenerics(DetailAST ast, int[] line, int after) { 226 // In a nested Generic type, so can only be a '>' or ',' or '&' 227 228 // In case of several extends definitions: 229 // 230 // class IntEnumValueType<E extends Enum<E> & IntEnum> 231 // ^ 232 // should be whitespace if followed by & -+ 233 // 234 final int indexOfAmp = IntStream.range(after, line.length) 235 .filter(index -> line[index] == '&') 236 .findFirst() 237 .orElse(-1); 238 if (indexOfAmp >= 1 239 && containsWhitespaceBetween(after, indexOfAmp, line)) { 240 if (indexOfAmp - after == 0) { 241 log(ast, MSG_WS_NOT_PRECEDED, "&"); 242 } 243 else if (indexOfAmp - after != 1) { 244 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 245 } 246 } 247 else if (line[after] == ' ') { 248 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 249 } 250 } 251 252 /** 253 * Process Single-generic. 254 * 255 * @param ast token 256 * @param line unicode code points array of line 257 * @param after position after 258 */ 259 private void processSingleGeneric(DetailAST ast, int[] line, int after) { 260 final char charAfter = Character.toChars(line[after])[0]; 261 if (isGenericBeforeMethod(ast) || isGenericBeforeCtor(ast)) { 262 if (Character.isWhitespace(charAfter)) { 263 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 264 } 265 } 266 else if (!isCharacterValidAfterGenericEnd(charAfter)) { 267 log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET); 268 } 269 } 270 271 /** 272 * Checks if generic is before constructor invocation. 273 * 274 * @param ast ast 275 * @return true if generic before a constructor invocation 276 */ 277 private static boolean isGenericBeforeCtor(DetailAST ast) { 278 final DetailAST parent = ast.getParent(); 279 return parent.getParent().getType() == TokenTypes.LITERAL_NEW 280 && (parent.getNextSibling().getType() == TokenTypes.IDENT 281 || parent.getNextSibling().getType() == TokenTypes.DOT); 282 } 283 284 /** 285 * Is generic before method reference. 286 * 287 * @param ast ast 288 * @return true if generic before a method ref 289 */ 290 private static boolean isGenericBeforeMethod(DetailAST ast) { 291 return ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL 292 || isAfterMethodReference(ast); 293 } 294 295 /** 296 * Checks if current generic end ('>') is located after 297 * {@link TokenTypes#METHOD_REF method reference operator}. 298 * 299 * @param genericEnd {@link TokenTypes#GENERIC_END} 300 * @return true if '>' follows after method reference. 301 */ 302 private static boolean isAfterMethodReference(DetailAST genericEnd) { 303 return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF; 304 } 305 306 /** 307 * Checks the token for the start of Generics. 308 * 309 * @param ast the token to check 310 */ 311 private void processStart(DetailAST ast) { 312 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 313 final int before = ast.getColumnNo() - 1; 314 final int after = ast.getColumnNo() + 1; 315 316 // Need to handle two cases as in: 317 // 318 // public static <T> Callable<T> callable(Runnable task, T result) 319 // ^ ^ 320 // ws reqd ---+ +--- whitespace NOT required 321 // 322 if (before >= 0) { 323 // Detect if the first case 324 final DetailAST parent = ast.getParent(); 325 final DetailAST grandparent = parent.getParent(); 326 if (grandparent.getType() == TokenTypes.CTOR_DEF 327 || grandparent.getType() == TokenTypes.METHOD_DEF 328 || isGenericBeforeCtor(ast)) { 329 // Require whitespace 330 if (!CommonUtil.isCodePointWhitespace(line, before)) { 331 log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET); 332 } 333 } 334 // Whitespace not required 335 else if (CommonUtil.isCodePointWhitespace(line, before) 336 && !containsWhitespaceBefore(before, line)) { 337 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET); 338 } 339 } 340 341 if (after < line.length 342 && CommonUtil.isCodePointWhitespace(line, after)) { 343 log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET); 344 } 345 } 346 347 /** 348 * Returns whether the specified string contains only whitespace between 349 * specified indices. 350 * 351 * @param fromIndex the index to start the search from. Inclusive 352 * @param toIndex the index to finish the search. Exclusive 353 * @param line the unicode code points array of line to check 354 * @return whether there are only whitespaces (or nothing) 355 */ 356 private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, int... line) { 357 boolean result = true; 358 for (int i = fromIndex; i < toIndex; i++) { 359 if (!CommonUtil.isCodePointWhitespace(line, i)) { 360 result = false; 361 break; 362 } 363 } 364 return result; 365 } 366 367 /** 368 * Returns whether the specified string contains only whitespace up to specified index. 369 * 370 * @param before the index to finish the search. Exclusive 371 * @param line the unicode code points array of line to check 372 * @return {@code true} if there are only whitespaces, 373 * false if there is nothing before or some other characters 374 */ 375 private static boolean containsWhitespaceBefore(int before, int... line) { 376 return before != 0 && CodePointUtil.hasWhitespaceBefore(before, line); 377 } 378 379 /** 380 * Checks whether given character is valid to be right after generic ends. 381 * 382 * @param charAfter character to check 383 * @return checks if given character is valid 384 */ 385 private static boolean isCharacterValidAfterGenericEnd(char charAfter) { 386 return charAfter == '(' || charAfter == ')' 387 || charAfter == ',' || charAfter == '[' 388 || charAfter == '.' || charAfter == ':' 389 || charAfter == ';' 390 || Character.isWhitespace(charAfter); 391 } 392 393}