001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 * <div> 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 * </div> 037 * 038 * <p> 039 * Left angle bracket ("<"): 040 * </p> 041 * <ul> 042 * <li> should be preceded with whitespace only 043 * in generic methods definitions.</li> 044 * <li> should not be preceded with whitespace 045 * when it is preceded method name or constructor.</li> 046 * <li> should not be preceded with whitespace when following type name.</li> 047 * <li> should not be followed with whitespace in all cases.</li> 048 * </ul> 049 * 050 * <p> 051 * Right angle bracket (">"): 052 * </p> 053 * <ul> 054 * <li> should not be preceded with whitespace in all cases.</li> 055 * <li> should be followed with whitespace in almost all cases, 056 * except diamond operators and when preceding a method name, constructor, or record header.</li> 057 * </ul> 058 * 059 * <p> 060 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 061 * </p> 062 * 063 * <p> 064 * Violation Message Keys: 065 * </p> 066 * <ul> 067 * <li> 068 * {@code ws.followed} 069 * </li> 070 * <li> 071 * {@code ws.illegalFollow} 072 * </li> 073 * <li> 074 * {@code ws.notPreceded} 075 * </li> 076 * <li> 077 * {@code ws.preceded} 078 * </li> 079 * </ul> 080 * 081 * @since 5.0 082 */ 083@FileStatefulCheck 084public class GenericWhitespaceCheck extends AbstractCheck { 085 086 /** 087 * A key is pointing to the warning message text in "messages.properties" 088 * file. 089 */ 090 public static final String MSG_WS_PRECEDED = "ws.preceded"; 091 092 /** 093 * A key is pointing to the warning message text in "messages.properties" 094 * file. 095 */ 096 public static final String MSG_WS_FOLLOWED = "ws.followed"; 097 098 /** 099 * A key is pointing to the warning message text in "messages.properties" 100 * file. 101 */ 102 public static final String MSG_WS_NOT_PRECEDED = "ws.notPreceded"; 103 104 /** 105 * A key is pointing to the warning message text in "messages.properties" 106 * file. 107 */ 108 public static final String MSG_WS_ILLEGAL_FOLLOW = "ws.illegalFollow"; 109 110 /** Open angle bracket literal. */ 111 private static final String OPEN_ANGLE_BRACKET = "<"; 112 113 /** Close angle bracket literal. */ 114 private static final String CLOSE_ANGLE_BRACKET = ">"; 115 116 /** Used to count the depth of a Generic expression. */ 117 private int depth; 118 119 @Override 120 public int[] getDefaultTokens() { 121 return getRequiredTokens(); 122 } 123 124 @Override 125 public int[] getAcceptableTokens() { 126 return getRequiredTokens(); 127 } 128 129 @Override 130 public int[] getRequiredTokens() { 131 return new int[] {TokenTypes.GENERIC_START, TokenTypes.GENERIC_END}; 132 } 133 134 @Override 135 public void beginTree(DetailAST rootAST) { 136 // Reset for each tree, just increase there are violations in preceding 137 // trees. 138 depth = 0; 139 } 140 141 @Override 142 public void visitToken(DetailAST ast) { 143 switch (ast.getType()) { 144 case TokenTypes.GENERIC_START: 145 processStart(ast); 146 depth++; 147 break; 148 case TokenTypes.GENERIC_END: 149 processEnd(ast); 150 depth--; 151 break; 152 default: 153 throw new IllegalArgumentException("Unknown type " + ast); 154 } 155 } 156 157 /** 158 * Checks the token for the end of Generics. 159 * 160 * @param ast the token to check 161 */ 162 private void processEnd(DetailAST ast) { 163 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 164 final int before = ast.getColumnNo() - 1; 165 final int after = ast.getColumnNo() + 1; 166 167 if (before >= 0 && CommonUtil.isCodePointWhitespace(line, before) 168 && !containsWhitespaceBefore(before, line)) { 169 log(ast, MSG_WS_PRECEDED, CLOSE_ANGLE_BRACKET); 170 } 171 172 if (after < line.length) { 173 // Check if the last Generic, in which case must be a whitespace 174 // or a '(),[.'. 175 if (depth == 1) { 176 processSingleGeneric(ast, line, after); 177 } 178 else { 179 processNestedGenerics(ast, line, after); 180 } 181 } 182 } 183 184 /** 185 * Process Nested generics. 186 * 187 * @param ast token 188 * @param line unicode code points array of line 189 * @param after position after 190 */ 191 private void processNestedGenerics(DetailAST ast, int[] line, int after) { 192 // In a nested Generic type, so can only be a '>' or ',' or '&' 193 194 // In case of several extends definitions: 195 // 196 // class IntEnumValueType<E extends Enum<E> & IntEnum> 197 // ^ 198 // should be whitespace if followed by & -+ 199 // 200 final int indexOfAmp = IntStream.range(after, line.length) 201 .filter(index -> line[index] == '&') 202 .findFirst() 203 .orElse(-1); 204 if (indexOfAmp >= 1 205 && containsWhitespaceBetween(after, indexOfAmp, line)) { 206 if (indexOfAmp - after == 0) { 207 log(ast, MSG_WS_NOT_PRECEDED, "&"); 208 } 209 else if (indexOfAmp - after != 1) { 210 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 211 } 212 } 213 else if (line[after] == ' ') { 214 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 215 } 216 } 217 218 /** 219 * Process Single-generic. 220 * 221 * @param ast token 222 * @param line unicode code points array of line 223 * @param after position after 224 */ 225 private void processSingleGeneric(DetailAST ast, int[] line, int after) { 226 final char charAfter = Character.toChars(line[after])[0]; 227 if (isGenericBeforeMethod(ast) 228 || isGenericBeforeCtorInvocation(ast) 229 || isGenericBeforeRecordHeader(ast)) { 230 if (Character.isWhitespace(charAfter)) { 231 log(ast, MSG_WS_FOLLOWED, CLOSE_ANGLE_BRACKET); 232 } 233 } 234 else if (!isCharacterValidAfterGenericEnd(charAfter)) { 235 log(ast, MSG_WS_ILLEGAL_FOLLOW, CLOSE_ANGLE_BRACKET); 236 } 237 } 238 239 /** 240 * Checks if generic is before record header. Identifies two cases: 241 * <ol> 242 * <li>In record def, eg: {@code record Session<T>()}</li> 243 * <li>In record pattern def, eg: {@code o instanceof Session<String>(var s)}</li> 244 * </ol> 245 * 246 * @param ast ast 247 * @return true if generic is before record header 248 */ 249 private static boolean isGenericBeforeRecordHeader(DetailAST ast) { 250 final DetailAST grandParent = ast.getParent().getParent(); 251 return grandParent.getType() == TokenTypes.RECORD_DEF 252 || grandParent.getParent().getType() == TokenTypes.RECORD_PATTERN_DEF; 253 } 254 255 /** 256 * Checks if generic is before constructor invocation. Identifies two cases: 257 * <ol> 258 * <li>{@code new ArrayList<>();}</li> 259 * <li>{@code new Outer.Inner<>();}</li> 260 * </ol> 261 * 262 * @param ast ast 263 * @return true if generic is before constructor invocation 264 */ 265 private static boolean isGenericBeforeCtorInvocation(DetailAST ast) { 266 final DetailAST grandParent = ast.getParent().getParent(); 267 return grandParent.getType() == TokenTypes.LITERAL_NEW 268 || grandParent.getParent().getType() == TokenTypes.LITERAL_NEW; 269 } 270 271 /** 272 * Checks if generic is after {@code LITERAL_NEW}. Identifies three cases: 273 * <ol> 274 * <li>{@code new <String>Object();}</li> 275 * <li>{@code new <String>Outer.Inner();}</li> 276 * <li>{@code new <@A Outer>@B Inner();}</li> 277 * </ol> 278 * 279 * @param ast ast 280 * @return true if generic after {@code LITERAL_NEW} 281 */ 282 private static boolean isGenericAfterNew(DetailAST ast) { 283 final DetailAST parent = ast.getParent(); 284 return parent.getParent().getType() == TokenTypes.LITERAL_NEW 285 && (parent.getNextSibling().getType() == TokenTypes.IDENT 286 || parent.getNextSibling().getType() == TokenTypes.DOT 287 || parent.getNextSibling().getType() == TokenTypes.ANNOTATIONS); 288 } 289 290 /** 291 * Is generic before method reference. 292 * 293 * @param ast ast 294 * @return true if generic before a method ref 295 */ 296 private static boolean isGenericBeforeMethod(DetailAST ast) { 297 return ast.getParent().getParent().getParent().getType() == TokenTypes.METHOD_CALL 298 || isAfterMethodReference(ast); 299 } 300 301 /** 302 * Checks if current generic end ('>') is located after 303 * {@link TokenTypes#METHOD_REF method reference operator}. 304 * 305 * @param genericEnd {@link TokenTypes#GENERIC_END} 306 * @return true if '>' follows after method reference. 307 */ 308 private static boolean isAfterMethodReference(DetailAST genericEnd) { 309 return genericEnd.getParent().getParent().getType() == TokenTypes.METHOD_REF; 310 } 311 312 /** 313 * Checks the token for the start of Generics. 314 * 315 * @param ast the token to check 316 */ 317 private void processStart(DetailAST ast) { 318 final int[] line = getLineCodePoints(ast.getLineNo() - 1); 319 final int before = ast.getColumnNo() - 1; 320 final int after = ast.getColumnNo() + 1; 321 322 // Checks if generic needs to be preceded by a whitespace or not. 323 // Handles 3 cases as in: 324 // 325 // public static <T> Callable<T> callable(Runnable task, T result) 326 // ^ ^ 327 // 1. ws reqd ---+ 2. +--- whitespace NOT required 328 // 329 // new <String>Object() 330 // ^ 331 // 3. +--- ws required 332 if (before >= 0) { 333 final DetailAST parent = ast.getParent(); 334 final DetailAST grandparent = parent.getParent(); 335 // cases (1, 3) where whitespace is required: 336 if (grandparent.getType() == TokenTypes.CTOR_DEF 337 || grandparent.getType() == TokenTypes.METHOD_DEF 338 || isGenericAfterNew(ast)) { 339 340 if (!CommonUtil.isCodePointWhitespace(line, before)) { 341 log(ast, MSG_WS_NOT_PRECEDED, OPEN_ANGLE_BRACKET); 342 } 343 } 344 // case 2 where whitespace is not required: 345 else if (CommonUtil.isCodePointWhitespace(line, before) 346 && !containsWhitespaceBefore(before, line)) { 347 log(ast, MSG_WS_PRECEDED, OPEN_ANGLE_BRACKET); 348 } 349 } 350 351 if (after < line.length 352 && CommonUtil.isCodePointWhitespace(line, after)) { 353 log(ast, MSG_WS_FOLLOWED, OPEN_ANGLE_BRACKET); 354 } 355 } 356 357 /** 358 * Returns whether the specified string contains only whitespace between 359 * specified indices. 360 * 361 * @param fromIndex the index to start the search from. Inclusive 362 * @param toIndex the index to finish the search. Exclusive 363 * @param line the unicode code points array of line to check 364 * @return whether there are only whitespaces (or nothing) 365 */ 366 private static boolean containsWhitespaceBetween(int fromIndex, int toIndex, int... line) { 367 boolean result = true; 368 for (int i = fromIndex; i < toIndex; i++) { 369 if (!CommonUtil.isCodePointWhitespace(line, i)) { 370 result = false; 371 break; 372 } 373 } 374 return result; 375 } 376 377 /** 378 * Returns whether the specified string contains only whitespace up to specified index. 379 * 380 * @param before the index to finish the search. Exclusive 381 * @param line the unicode code points array of line to check 382 * @return {@code true} if there are only whitespaces, 383 * false if there is nothing before or some other characters 384 */ 385 private static boolean containsWhitespaceBefore(int before, int... line) { 386 return before != 0 && CodePointUtil.hasWhitespaceBefore(before, line); 387 } 388 389 /** 390 * Checks whether given character is valid to be right after generic ends. 391 * 392 * @param charAfter character to check 393 * @return checks if given character is valid 394 */ 395 private static boolean isCharacterValidAfterGenericEnd(char charAfter) { 396 return charAfter == ')' || charAfter == ',' 397 || charAfter == '[' || charAfter == '.' 398 || charAfter == ':' || charAfter == ';' 399 || Character.isWhitespace(charAfter); 400 } 401 402}