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