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 com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027 028/** 029 * <div> 030 * Checks that the whitespace around square-bracket tokens {@code [} and {@code ]} 031 * follows the Google Java Style Guide requirements for array declarations, array creation, 032 * and array indexing. 033 * </div> 034 * 035 * <p> 036 * Left square bracket ("{@code [}"): 037 * </p> 038 * <ul> 039 * <li>must not be preceded with whitespace when preceded by a 040 * {@code TYPE} or {@code IDENT} in array declarations or array access</li> 041 * <li>must not be followed with whitespace</li> 042 * </ul> 043 * 044 * <p> 045 * Right square bracket ("{@code ]}"): 046 * </p> 047 * <ul> 048 * <li>must not be preceded with whitespace</li> 049 * <li>must be followed with whitespace in all cases, except when followed by: 050 * <ul> 051 * <li>another bracket: {@code [][]}</li> 052 * <li>a dot for member access: {@code arr[i].length}</li> 053 * <li>a comma or semicolon: {@code arr[i],} or {@code arr[i];}</li> 054 * <li>postfix operators: {@code arr[i]++} or {@code arr[i]--}</li> 055 * <li>a right parenthesis or another closing construct: {@code (arr[i])}</li> 056 * </ul> 057 * </li> 058 * </ul> 059 * 060 * @since 13.1.0 061 */ 062@StatelessCheck 063public class ArrayBracketNoWhitespaceCheck extends AbstractCheck { 064 065 /** 066 * A key is pointing to the warning message text in "messages.properties" 067 * file. 068 */ 069 public static final String MSG_WS_PRECEDED = "ws.preceded"; 070 071 /** 072 * A key is pointing to the warning message text in "messages.properties" 073 * file. 074 */ 075 public static final String MSG_WS_FOLLOWED = "ws.followed"; 076 077 /** 078 * A key is pointing to the warning message text in "messages.properties" 079 * file. 080 */ 081 public static final String MSG_WS_NOT_FOLLOWED = "ws.notFollowed"; 082 083 /** Open square bracket literal. */ 084 private static final String LEFT_BRACKET = "["; 085 086 /** Close square bracket literal. */ 087 private static final String RIGHT_BRACKET = "]"; 088 089 @Override 090 public int[] getDefaultTokens() { 091 return getRequiredTokens(); 092 } 093 094 @Override 095 public int[] getAcceptableTokens() { 096 return getRequiredTokens(); 097 } 098 099 @Override 100 public int[] getRequiredTokens() { 101 return new int[] { 102 TokenTypes.ARRAY_DECLARATOR, 103 TokenTypes.INDEX_OP, 104 }; 105 } 106 107 @Override 108 public void visitToken(DetailAST ast) { 109 // Check left bracket 110 if (precededByWhitespace(ast)) { 111 log(ast, MSG_WS_PRECEDED, LEFT_BRACKET); 112 } 113 if (followedByWhitespace(ast)) { 114 log(ast, MSG_WS_FOLLOWED, LEFT_BRACKET); 115 } 116 117 // Check right bracket 118 final DetailAST rightBracket = ast.findFirstToken(TokenTypes.RBRACK); 119 if (precededByWhitespace(rightBracket)) { 120 log(rightBracket, MSG_WS_PRECEDED, RIGHT_BRACKET); 121 } 122 123 // Check whitespace after right bracket 124 final int[] line = getLineCodePoints(rightBracket.getLineNo() - 1); 125 final int bracketEnd = rightBracket.getColumnNo() + 1; 126 127 if (bracketEnd < line.length) { 128 final DetailAST parent = rightBracket.getParent(); 129 final DetailAST nextToken = findNextToken(parent, rightBracket); 130 131 if (nextToken != null) { 132 final boolean hasWhitespace = CommonUtil.isCodePointWhitespace(line, bracketEnd); 133 final boolean requiresWhitespace = !isValidWithoutWhitespace(nextToken); 134 135 if (requiresWhitespace && !hasWhitespace) { 136 log(rightBracket, MSG_WS_NOT_FOLLOWED, RIGHT_BRACKET); 137 } 138 else if (!requiresWhitespace && hasWhitespace) { 139 log(rightBracket, MSG_WS_FOLLOWED, RIGHT_BRACKET); 140 } 141 } 142 } 143 } 144 145 /** 146 * Checks if a token is preceded by whitespace. 147 * 148 * @param token the token to check 149 * @return true if preceded by whitespace, false otherwise 150 */ 151 private boolean precededByWhitespace(DetailAST token) { 152 final int[] line = getLineCodePoints(token.getLineNo() - 1); 153 final int columnNo = token.getColumnNo(); 154 final int before = columnNo - 1; 155 156 return before >= 0 && CommonUtil.isCodePointWhitespace(line, before); 157 } 158 159 /** 160 * Checks if a token is followed by whitespace. 161 * 162 * @param token the token to check 163 * @return true if followed by whitespace, false otherwise 164 */ 165 private boolean followedByWhitespace(DetailAST token) { 166 final int[] line = getLineCodePoints(token.getLineNo() - 1); 167 final int columnNo = token.getColumnNo(); 168 final int after = columnNo + 1; 169 170 return after < line.length && CommonUtil.isCodePointWhitespace(line, after); 171 } 172 173 /** 174 * Finds the next token after a right bracket by searching in a limited scope. 175 * 176 * @param arrayToken the array token (ARRAY_DECLARATOR or INDEX_OP) 177 * @param rightBracket the right bracket token for position checking 178 * @return the next token that appears after the bracket, or null if none found 179 */ 180 private static DetailAST findNextToken(DetailAST arrayToken, DetailAST rightBracket) { 181 final int bracketLine = rightBracket.getLineNo(); 182 final int bracketCol = rightBracket.getColumnNo(); 183 184 // Traverse up a limited number of levels to find a reasonable search scope 185 DetailAST searchRoot = arrayToken; 186 final int maxLevels = 5; 187 188 for (int level = 0; level < maxLevels; level++) { 189 searchRoot = searchRoot.getParent(); 190 } 191 192 return findClosestTokenAfter(searchRoot, bracketLine, bracketCol); 193 } 194 195 /** 196 * Recursively scans the AST to find the token closest to and after the given position. 197 * 198 * @param node the current node to scan 199 * @param line the line number of the bracket 200 * @param column the column number of the bracket 201 * @return the closest token after the position, or null if none found 202 */ 203 private static DetailAST findClosestTokenAfter(DetailAST node, int line, int column) { 204 DetailAST closest = null; 205 int closestDistance = Integer.MAX_VALUE; 206 207 // Check current node (skip RBRACK and structural tokens) 208 if (isValidTokenToCheck(node) 209 && isTokenAfter(node, line, column) && node.getLineNo() == line) { 210 final int distance = node.getColumnNo() - column; 211 closest = node; 212 closestDistance = distance; 213 } 214 215 DetailAST child = node.getFirstChild(); 216 while (child != null) { 217 final DetailAST candidate = findClosestTokenAfter(child, line, column); 218 if (candidate != null) { 219 final int distance = candidate.getColumnNo() - column; 220 if (distance < closestDistance) { 221 closest = candidate; 222 closestDistance = distance; 223 } 224 } 225 226 child = child.getNextSibling(); 227 } 228 229 return closest; 230 } 231 232 /** 233 * Checks if a token is valid to consider as the "next" token. 234 * Filters out structural/container tokens that don't represent actual code. 235 * 236 * @param node the token to check 237 * @return true if this is a valid token to check 238 */ 239 private static boolean isValidTokenToCheck(DetailAST node) { 240 final int type = node.getType(); 241 242 return !isStructuralToken(type); 243 } 244 245 /** 246 * Checks if a token type is a structural/container token. 247 * 248 * @param type the token type to check 249 * @return true if this is a structural token 250 */ 251 private static boolean isStructuralToken(int type) { 252 return type == TokenTypes.RBRACK 253 || isExpressionContainer(type) 254 || isTypeContainer(type) 255 || isDeclarationContainer(type); 256 } 257 258 /** 259 * Checks if a token type is an expression container. 260 * 261 * @param type the token type to check 262 * @return true if this is an expression container 263 */ 264 private static boolean isExpressionContainer(int type) { 265 return type == TokenTypes.EXPR || type == TokenTypes.ELIST; 266 } 267 268 /** 269 * Checks if a token type is a type-related container. 270 * 271 * @param type the token type to check 272 * @return true if this is a type container 273 */ 274 private static boolean isTypeContainer(int type) { 275 return type == TokenTypes.TYPE 276 || type == TokenTypes.TYPE_ARGUMENTS 277 || type == TokenTypes.TYPE_ARGUMENT; 278 } 279 280 /** 281 * Checks if a token type is a declaration/definition container. 282 * 283 * @param type the token type to check 284 * @return true if this is a declaration container 285 */ 286 private static boolean isDeclarationContainer(int type) { 287 return type == TokenTypes.VARIABLE_DEF 288 || type == TokenTypes.MODIFIERS 289 || type == TokenTypes.ANNOTATIONS 290 || type == TokenTypes.OBJBLOCK; 291 } 292 293 /** 294 * Checks if a token appears after the given position in the source code. 295 * 296 * @param token the token to check 297 * @param line the line number to compare against 298 * @param column the column number to compare against 299 * @return true if the token is on a later line or same line but later column 300 */ 301 private static boolean isTokenAfter(DetailAST token, int line, int column) { 302 return token.getLineNo() > line 303 || token.getLineNo() == line && token.getColumnNo() > column; 304 } 305 306 /** 307 * Checks if the given token can follow a right bracket without whitespace. 308 * Uses TokenTypes to determine valid tokens. 309 * 310 * @param nextToken the token that follows the right bracket 311 * @return true if the token can follow without whitespace 312 */ 313 private static boolean isValidWithoutWhitespace(DetailAST nextToken) { 314 final int nextType = nextToken.getType(); 315 316 return isBracketOrAccessor(nextType) 317 || isPunctuation(nextType) 318 || isPostfixOrShift(nextType); 319 } 320 321 /** 322 * Checks if a token type is a bracket or accessor. 323 * 324 * @param type the token type to check 325 * @return true if this is a bracket or accessor 326 */ 327 private static boolean isBracketOrAccessor(int type) { 328 return type == TokenTypes.ARRAY_DECLARATOR 329 || type == TokenTypes.INDEX_OP 330 || type == TokenTypes.DOT 331 || type == TokenTypes.METHOD_REF; 332 } 333 334 /** 335 * Checks if a token type is punctuation that can follow without whitespace. 336 * 337 * @param type the token type to check 338 * @return true if this is valid punctuation 339 */ 340 private static boolean isPunctuation(int type) { 341 return type == TokenTypes.COMMA 342 || type == TokenTypes.SEMI 343 || type == TokenTypes.RPAREN 344 || type == TokenTypes.RCURLY 345 || type == TokenTypes.GENERIC_END; 346 } 347 348 /** 349 * Checks if a token type is a postfix operator or shift operator. 350 * 351 * @param type the token type to check 352 * @return true if this is a postfix or shift operator 353 */ 354 private static boolean isPostfixOrShift(int type) { 355 return type == TokenTypes.POST_INC 356 || type == TokenTypes.POST_DEC 357 || type == TokenTypes.SL 358 || type == TokenTypes.SR; 359 } 360}