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