001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 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 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 088 * </p> 089 * <p> 090 * Violation Message Keys: 091 * </p> 092 * <ul> 093 * <li> 094 * {@code block.empty} 095 * </li> 096 * <li> 097 * {@code block.noStatement} 098 * </li> 099 * </ul> 100 * 101 * @since 3.0 102 */ 103@StatelessCheck 104public class EmptyBlockCheck 105 extends AbstractCheck { 106 107 /** 108 * A key is pointing to the warning message text in "messages.properties" 109 * file. 110 */ 111 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement"; 112 113 /** 114 * A key is pointing to the warning message text in "messages.properties" 115 * file. 116 */ 117 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 118 119 /** Specify the policy on block contents. */ 120 private BlockOption option = BlockOption.STATEMENT; 121 122 /** 123 * Setter to specify the policy on block contents. 124 * 125 * @param optionStr string to decode option from 126 * @throws IllegalArgumentException if unable to decode 127 * @since 3.0 128 */ 129 public void setOption(String optionStr) { 130 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 131 } 132 133 @Override 134 public int[] getDefaultTokens() { 135 return new int[] { 136 TokenTypes.LITERAL_WHILE, 137 TokenTypes.LITERAL_TRY, 138 TokenTypes.LITERAL_FINALLY, 139 TokenTypes.LITERAL_DO, 140 TokenTypes.LITERAL_IF, 141 TokenTypes.LITERAL_ELSE, 142 TokenTypes.LITERAL_FOR, 143 TokenTypes.INSTANCE_INIT, 144 TokenTypes.STATIC_INIT, 145 TokenTypes.LITERAL_SWITCH, 146 TokenTypes.LITERAL_SYNCHRONIZED, 147 }; 148 } 149 150 @Override 151 public int[] getAcceptableTokens() { 152 return new int[] { 153 TokenTypes.LITERAL_WHILE, 154 TokenTypes.LITERAL_TRY, 155 TokenTypes.LITERAL_CATCH, 156 TokenTypes.LITERAL_FINALLY, 157 TokenTypes.LITERAL_DO, 158 TokenTypes.LITERAL_IF, 159 TokenTypes.LITERAL_ELSE, 160 TokenTypes.LITERAL_FOR, 161 TokenTypes.INSTANCE_INIT, 162 TokenTypes.STATIC_INIT, 163 TokenTypes.LITERAL_SWITCH, 164 TokenTypes.LITERAL_SYNCHRONIZED, 165 TokenTypes.LITERAL_CASE, 166 TokenTypes.LITERAL_DEFAULT, 167 TokenTypes.ARRAY_INIT, 168 }; 169 } 170 171 @Override 172 public int[] getRequiredTokens() { 173 return CommonUtil.EMPTY_INT_ARRAY; 174 } 175 176 @Override 177 public void visitToken(DetailAST ast) { 178 final DetailAST leftCurly = findLeftCurly(ast); 179 if (leftCurly != null) { 180 if (option == BlockOption.STATEMENT) { 181 final boolean emptyBlock; 182 if (leftCurly.getType() == TokenTypes.LCURLY) { 183 final DetailAST nextSibling = leftCurly.getNextSibling(); 184 emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP 185 && nextSibling.getType() != TokenTypes.SWITCH_RULE; 186 } 187 else { 188 emptyBlock = leftCurly.getChildCount() <= 1; 189 } 190 if (emptyBlock) { 191 log(leftCurly, 192 MSG_KEY_BLOCK_NO_STATEMENT); 193 } 194 } 195 else if (!hasText(leftCurly)) { 196 log(leftCurly, 197 MSG_KEY_BLOCK_EMPTY, 198 ast.getText()); 199 } 200 } 201 } 202 203 /** 204 * Checks if SLIST token contains any text. 205 * 206 * @param slistAST a {@code DetailAST} value 207 * @return whether the SLIST token contains any text. 208 */ 209 private boolean hasText(final DetailAST slistAST) { 210 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 211 final DetailAST rcurlyAST; 212 213 if (rightCurly == null) { 214 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 215 } 216 else { 217 rcurlyAST = rightCurly; 218 } 219 final int slistLineNo = slistAST.getLineNo(); 220 final int slistColNo = slistAST.getColumnNo(); 221 final int rcurlyLineNo = rcurlyAST.getLineNo(); 222 final int rcurlyColNo = rcurlyAST.getColumnNo(); 223 boolean returnValue = false; 224 if (slistLineNo == rcurlyLineNo) { 225 // Handle braces on the same line 226 final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1), 227 slistColNo + 1, rcurlyColNo); 228 229 if (!CodePointUtil.isBlank(txt)) { 230 returnValue = true; 231 } 232 } 233 else { 234 final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1); 235 final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine, 236 slistColNo + 1, codePointsFirstLine.length); 237 final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1); 238 final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo); 239 // check if all lines are also only whitespace 240 returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine)) 241 || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo); 242 } 243 return returnValue; 244 } 245 246 /** 247 * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive) 248 * contain whitespaces only. 249 * 250 * @param lineFrom 251 * check from this line number 252 * @param lineTo 253 * check to this line numbers 254 * @return true if lines contain only whitespaces 255 */ 256 private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) { 257 boolean result = true; 258 for (int i = lineFrom; i < lineTo - 1; i++) { 259 if (!CodePointUtil.isBlank(getLineCodePoints(i))) { 260 result = false; 261 break; 262 } 263 } 264 return result; 265 } 266 267 /** 268 * Calculates the left curly corresponding to the block to be checked. 269 * 270 * @param ast a {@code DetailAST} value 271 * @return the left curly corresponding to the block to be checked 272 */ 273 private static DetailAST findLeftCurly(DetailAST ast) { 274 final DetailAST leftCurly; 275 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 276 if ((ast.getType() == TokenTypes.LITERAL_CASE 277 || ast.getType() == TokenTypes.LITERAL_DEFAULT) 278 && ast.getNextSibling() != null 279 && ast.getNextSibling().getFirstChild() != null 280 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) { 281 leftCurly = ast.getNextSibling().getFirstChild(); 282 } 283 else if (slistAST == null) { 284 leftCurly = ast.findFirstToken(TokenTypes.LCURLY); 285 } 286 else { 287 leftCurly = slistAST; 288 } 289 return leftCurly; 290 } 291 292}