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 * This check processes LITERAL_CASE and LITERAL_DEFAULT separately. 047 * So, if tokens=LITERAL_DEFAULT, following code will not trigger any violation, 048 * as the empty block belongs to LITERAL_CASE: 049 * </p> 050 * <p> 051 * Configuration: 052 * </p> 053 * <pre> 054 * <module name="EmptyBlock"> 055 * <property name="tokens" value="LITERAL_DEFAULT"/> 056 * </module> 057 * </pre> 058 * <p> 059 * Result: 060 * </p> 061 * <pre> 062 * switch (a) { 063 * default: // no violation for "default:" as empty block belong to "case 1:" 064 * case 1: { } 065 * } 066 * </pre> 067 * <ul> 068 * <li> 069 * Property {@code option} - specify the policy on block contents. 070 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}. 071 * Default value is {@code statement}. 072 * </li> 073 * <li> 074 * Property {@code tokens} - tokens to check 075 * Type is {@code int[]}. 076 * Default value is: 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 078 * LITERAL_WHILE</a>, 079 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY"> 080 * LITERAL_TRY</a>, 081 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY"> 082 * LITERAL_FINALLY</a>, 083 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 084 * LITERAL_DO</a>, 085 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 086 * LITERAL_IF</a>, 087 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 088 * LITERAL_ELSE</a>, 089 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 090 * LITERAL_FOR</a>, 091 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT"> 092 * INSTANCE_INIT</a>, 093 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 094 * STATIC_INIT</a>, 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 096 * LITERAL_SWITCH</a>, 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 098 * LITERAL_SYNCHRONIZED</a>. 099 * </li> 100 * </ul> 101 * <p> 102 * To configure the check: 103 * </p> 104 * <pre> 105 * <module name="EmptyBlock"/> 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> 117 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 118 * </p> 119 * <p> 120 * Violation Message Keys: 121 * </p> 122 * <ul> 123 * <li> 124 * {@code block.empty} 125 * </li> 126 * <li> 127 * {@code block.noStatement} 128 * </li> 129 * </ul> 130 * 131 * @since 3.0 132 */ 133@StatelessCheck 134public class EmptyBlockCheck 135 extends AbstractCheck { 136 137 /** 138 * A key is pointing to the warning message text in "messages.properties" 139 * file. 140 */ 141 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement"; 142 143 /** 144 * A key is pointing to the warning message text in "messages.properties" 145 * file. 146 */ 147 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 148 149 /** Specify the policy on block contents. */ 150 private BlockOption option = BlockOption.STATEMENT; 151 152 /** 153 * Setter to specify the policy on block contents. 154 * 155 * @param optionStr string to decode option from 156 * @throws IllegalArgumentException if unable to decode 157 */ 158 public void setOption(String optionStr) { 159 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 160 } 161 162 @Override 163 public int[] getDefaultTokens() { 164 return new int[] { 165 TokenTypes.LITERAL_WHILE, 166 TokenTypes.LITERAL_TRY, 167 TokenTypes.LITERAL_FINALLY, 168 TokenTypes.LITERAL_DO, 169 TokenTypes.LITERAL_IF, 170 TokenTypes.LITERAL_ELSE, 171 TokenTypes.LITERAL_FOR, 172 TokenTypes.INSTANCE_INIT, 173 TokenTypes.STATIC_INIT, 174 TokenTypes.LITERAL_SWITCH, 175 TokenTypes.LITERAL_SYNCHRONIZED, 176 }; 177 } 178 179 @Override 180 public int[] getAcceptableTokens() { 181 return new int[] { 182 TokenTypes.LITERAL_WHILE, 183 TokenTypes.LITERAL_TRY, 184 TokenTypes.LITERAL_CATCH, 185 TokenTypes.LITERAL_FINALLY, 186 TokenTypes.LITERAL_DO, 187 TokenTypes.LITERAL_IF, 188 TokenTypes.LITERAL_ELSE, 189 TokenTypes.LITERAL_FOR, 190 TokenTypes.INSTANCE_INIT, 191 TokenTypes.STATIC_INIT, 192 TokenTypes.LITERAL_SWITCH, 193 TokenTypes.LITERAL_SYNCHRONIZED, 194 TokenTypes.LITERAL_CASE, 195 TokenTypes.LITERAL_DEFAULT, 196 TokenTypes.ARRAY_INIT, 197 }; 198 } 199 200 @Override 201 public int[] getRequiredTokens() { 202 return CommonUtil.EMPTY_INT_ARRAY; 203 } 204 205 @Override 206 public void visitToken(DetailAST ast) { 207 final DetailAST leftCurly = findLeftCurly(ast); 208 if (leftCurly != null) { 209 if (option == BlockOption.STATEMENT) { 210 final boolean emptyBlock; 211 if (leftCurly.getType() == TokenTypes.LCURLY) { 212 emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP; 213 } 214 else { 215 emptyBlock = leftCurly.getChildCount() <= 1; 216 } 217 if (emptyBlock) { 218 log(leftCurly, 219 MSG_KEY_BLOCK_NO_STATEMENT, 220 ast.getText()); 221 } 222 } 223 else if (!hasText(leftCurly)) { 224 log(leftCurly, 225 MSG_KEY_BLOCK_EMPTY, 226 ast.getText()); 227 } 228 } 229 } 230 231 /** 232 * Checks if SLIST token contains any text. 233 * 234 * @param slistAST a {@code DetailAST} value 235 * @return whether the SLIST token contains any text. 236 */ 237 private boolean hasText(final DetailAST slistAST) { 238 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 239 final DetailAST rcurlyAST; 240 241 if (rightCurly == null) { 242 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 243 } 244 else { 245 rcurlyAST = rightCurly; 246 } 247 final int slistLineNo = slistAST.getLineNo(); 248 final int slistColNo = slistAST.getColumnNo(); 249 final int rcurlyLineNo = rcurlyAST.getLineNo(); 250 final int rcurlyColNo = rcurlyAST.getColumnNo(); 251 final String[] lines = getLines(); 252 boolean returnValue = false; 253 if (slistLineNo == rcurlyLineNo) { 254 // Handle braces on the same line 255 final String txt = lines[slistLineNo - 1] 256 .substring(slistColNo + 1, rcurlyColNo); 257 if (!CommonUtil.isBlank(txt)) { 258 returnValue = true; 259 } 260 } 261 else { 262 final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1); 263 final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo); 264 // check if all lines are also only whitespace 265 returnValue = !(CommonUtil.isBlank(firstLine) && CommonUtil.isBlank(lastLine)) 266 || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo); 267 } 268 return returnValue; 269 } 270 271 /** 272 * Checks is all lines in array contain whitespaces only. 273 * 274 * @param lines 275 * array of lines 276 * @param lineFrom 277 * check from this line number 278 * @param lineTo 279 * check to this line numbers 280 * @return true if lines contain only whitespaces 281 */ 282 private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) { 283 boolean result = true; 284 for (int i = lineFrom; i < lineTo - 1; i++) { 285 if (!CommonUtil.isBlank(lines[i])) { 286 result = false; 287 break; 288 } 289 } 290 return result; 291 } 292 293 /** 294 * Calculates the left curly corresponding to the block to be checked. 295 * 296 * @param ast a {@code DetailAST} value 297 * @return the left curly corresponding to the block to be checked 298 */ 299 private static DetailAST findLeftCurly(DetailAST ast) { 300 final DetailAST leftCurly; 301 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 302 if ((ast.getType() == TokenTypes.LITERAL_CASE 303 || ast.getType() == TokenTypes.LITERAL_DEFAULT) 304 && ast.getNextSibling() != null 305 && ast.getNextSibling().getFirstChild() != null 306 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) { 307 leftCurly = ast.getNextSibling().getFirstChild(); 308 } 309 else if (slistAST == null) { 310 leftCurly = ast.findFirstToken(TokenTypes.LCURLY); 311 } 312 else { 313 leftCurly = slistAST; 314 } 315 return leftCurly; 316 } 317 318}