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