001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.blocks; 021 022import java.util.Arrays; 023import java.util.Locale; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.CodePointUtil; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 031 032/** 033 * <p> 034 * Checks for empty blocks. This check does not validate sequential blocks. 035 * </p> 036 * <p> 037 * Sequential blocks won't be checked. Also, no violations for fallthrough: 038 * </p> 039 * <pre> 040 * switch (a) { 041 * case 1: // no violation 042 * case 2: // no violation 043 * case 3: someMethod(); { } // no violation 044 * default: break; 045 * } 046 * </pre> 047 * <p> 048 * NOTE: This check processes LITERAL_CASE and LITERAL_DEFAULT separately. 049 * Verification empty block is done for single most nearest {@code case} or {@code default}. 050 * </p> 051 * <ul> 052 * <li> 053 * Property {@code option} - specify the policy on block contents. 054 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}. 055 * Default value is {@code statement}. 056 * </li> 057 * <li> 058 * Property {@code tokens} - tokens to check 059 * Type is {@code java.lang.String[]}. 060 * Validation type is {@code tokenSet}. 061 * Default value is: 062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 063 * LITERAL_WHILE</a>, 064 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY"> 065 * LITERAL_TRY</a>, 066 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY"> 067 * LITERAL_FINALLY</a>, 068 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 069 * LITERAL_DO</a>, 070 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 071 * LITERAL_IF</a>, 072 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 073 * LITERAL_ELSE</a>, 074 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 075 * LITERAL_FOR</a>, 076 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT"> 077 * INSTANCE_INIT</a>, 078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 079 * STATIC_INIT</a>, 080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 081 * LITERAL_SWITCH</a>, 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 083 * LITERAL_SYNCHRONIZED</a>. 084 * </li> 085 * </ul> 086 * <p> 087 * To configure the check: 088 * </p> 089 * <pre> 090 * <module name="EmptyBlock"/> 091 * </pre> 092 * <p> 093 * Example: 094 * </p> 095 * <pre> 096 * public class Test { 097 * private void emptyLoop() { 098 * for (int i = 0; i < 10; i++) { // violation 099 * } 100 * 101 * try { // violation 102 * 103 * } catch (Exception e) { 104 * // ignored 105 * } 106 * } 107 * } 108 * </pre> 109 * <p> 110 * To configure the check for the {@code text} policy and only {@code try} blocks: 111 * </p> 112 * <pre> 113 * <module name="EmptyBlock"> 114 * <property name="option" value="text"/> 115 * <property name="tokens" value="LITERAL_TRY"/> 116 * </module> 117 * </pre> 118 * <p> Example: </p> 119 * <pre> 120 * public class Test { 121 * private void emptyLoop() { 122 * for (int i = 0; i < 10; i++) { 123 * // ignored 124 * } 125 * 126 * // violation on next line 127 * try { 128 * 129 * } catch (Exception e) { 130 * // ignored 131 * } 132 * } 133 * } 134 * </pre> 135 * <p> 136 * To configure the check for default in switch block: 137 * </p> 138 * <pre> 139 * <module name="EmptyBlock"> 140 * <property name="tokens" value="LITERAL_DEFAULT"/> 141 * </module> 142 * </pre> 143 * <p> Example: </p> 144 * <pre> 145 * public class Test { 146 * private void test(int a) { 147 * switch (a) { 148 * case 1: someMethod(); 149 * default: // OK, as there is no block 150 * } 151 * switch (a) { 152 * case 1: someMethod(); 153 * default: {} // violation 154 * } 155 * } 156 * } 157 * </pre> 158 * <p> 159 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 160 * </p> 161 * <p> 162 * Violation Message Keys: 163 * </p> 164 * <ul> 165 * <li> 166 * {@code block.empty} 167 * </li> 168 * <li> 169 * {@code block.noStatement} 170 * </li> 171 * </ul> 172 * 173 * @since 3.0 174 */ 175@StatelessCheck 176public class EmptyBlockCheck 177 extends AbstractCheck { 178 179 /** 180 * A key is pointing to the warning message text in "messages.properties" 181 * file. 182 */ 183 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement"; 184 185 /** 186 * A key is pointing to the warning message text in "messages.properties" 187 * file. 188 */ 189 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 190 191 /** Specify the policy on block contents. */ 192 private BlockOption option = BlockOption.STATEMENT; 193 194 /** 195 * Setter to specify the policy on block contents. 196 * 197 * @param optionStr string to decode option from 198 * @throws IllegalArgumentException if unable to decode 199 */ 200 public void setOption(String optionStr) { 201 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 202 } 203 204 @Override 205 public int[] getDefaultTokens() { 206 return new int[] { 207 TokenTypes.LITERAL_WHILE, 208 TokenTypes.LITERAL_TRY, 209 TokenTypes.LITERAL_FINALLY, 210 TokenTypes.LITERAL_DO, 211 TokenTypes.LITERAL_IF, 212 TokenTypes.LITERAL_ELSE, 213 TokenTypes.LITERAL_FOR, 214 TokenTypes.INSTANCE_INIT, 215 TokenTypes.STATIC_INIT, 216 TokenTypes.LITERAL_SWITCH, 217 TokenTypes.LITERAL_SYNCHRONIZED, 218 }; 219 } 220 221 @Override 222 public int[] getAcceptableTokens() { 223 return new int[] { 224 TokenTypes.LITERAL_WHILE, 225 TokenTypes.LITERAL_TRY, 226 TokenTypes.LITERAL_CATCH, 227 TokenTypes.LITERAL_FINALLY, 228 TokenTypes.LITERAL_DO, 229 TokenTypes.LITERAL_IF, 230 TokenTypes.LITERAL_ELSE, 231 TokenTypes.LITERAL_FOR, 232 TokenTypes.INSTANCE_INIT, 233 TokenTypes.STATIC_INIT, 234 TokenTypes.LITERAL_SWITCH, 235 TokenTypes.LITERAL_SYNCHRONIZED, 236 TokenTypes.LITERAL_CASE, 237 TokenTypes.LITERAL_DEFAULT, 238 TokenTypes.ARRAY_INIT, 239 }; 240 } 241 242 @Override 243 public int[] getRequiredTokens() { 244 return CommonUtil.EMPTY_INT_ARRAY; 245 } 246 247 @Override 248 public void visitToken(DetailAST ast) { 249 final DetailAST leftCurly = findLeftCurly(ast); 250 if (leftCurly != null) { 251 if (option == BlockOption.STATEMENT) { 252 final boolean emptyBlock; 253 if (leftCurly.getType() == TokenTypes.LCURLY) { 254 final DetailAST nextSibling = leftCurly.getNextSibling(); 255 emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP 256 && nextSibling.getType() != TokenTypes.SWITCH_RULE; 257 } 258 else { 259 emptyBlock = leftCurly.getChildCount() <= 1; 260 } 261 if (emptyBlock) { 262 log(leftCurly, 263 MSG_KEY_BLOCK_NO_STATEMENT, 264 ast.getText()); 265 } 266 } 267 else if (!hasText(leftCurly)) { 268 log(leftCurly, 269 MSG_KEY_BLOCK_EMPTY, 270 ast.getText()); 271 } 272 } 273 } 274 275 /** 276 * Checks if SLIST token contains any text. 277 * 278 * @param slistAST a {@code DetailAST} value 279 * @return whether the SLIST token contains any text. 280 */ 281 private boolean hasText(final DetailAST slistAST) { 282 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 283 final DetailAST rcurlyAST; 284 285 if (rightCurly == null) { 286 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 287 } 288 else { 289 rcurlyAST = rightCurly; 290 } 291 final int slistLineNo = slistAST.getLineNo(); 292 final int slistColNo = slistAST.getColumnNo(); 293 final int rcurlyLineNo = rcurlyAST.getLineNo(); 294 final int rcurlyColNo = rcurlyAST.getColumnNo(); 295 boolean returnValue = false; 296 if (slistLineNo == rcurlyLineNo) { 297 // Handle braces on the same line 298 final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1), 299 slistColNo + 1, rcurlyColNo); 300 301 if (!CodePointUtil.isBlank(txt)) { 302 returnValue = true; 303 } 304 } 305 else { 306 final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1); 307 final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine, 308 slistColNo + 1, codePointsFirstLine.length); 309 final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1); 310 final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo); 311 // check if all lines are also only whitespace 312 returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine)) 313 || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo); 314 } 315 return returnValue; 316 } 317 318 /** 319 * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive) 320 * contain whitespaces only. 321 * 322 * @param lineFrom 323 * check from this line number 324 * @param lineTo 325 * check to this line numbers 326 * @return true if lines contain only whitespaces 327 */ 328 private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) { 329 boolean result = true; 330 for (int i = lineFrom; i < lineTo - 1; i++) { 331 if (!CodePointUtil.isBlank(getLineCodePoints(i))) { 332 result = false; 333 break; 334 } 335 } 336 return result; 337 } 338 339 /** 340 * Calculates the left curly corresponding to the block to be checked. 341 * 342 * @param ast a {@code DetailAST} value 343 * @return the left curly corresponding to the block to be checked 344 */ 345 private static DetailAST findLeftCurly(DetailAST ast) { 346 final DetailAST leftCurly; 347 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 348 if ((ast.getType() == TokenTypes.LITERAL_CASE 349 || ast.getType() == TokenTypes.LITERAL_DEFAULT) 350 && ast.getNextSibling() != null 351 && ast.getNextSibling().getFirstChild() != null 352 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) { 353 leftCurly = ast.getNextSibling().getFirstChild(); 354 } 355 else if (slistAST == null) { 356 leftCurly = ast.findFirstToken(TokenTypes.LCURLY); 357 } 358 else { 359 leftCurly = slistAST; 360 } 361 return leftCurly; 362 } 363 364}