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