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; 024import java.util.Optional; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CodePointUtil; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 032 033/** 034 * <div> 035 * Checks for empty blocks. 036 * </div> 037 * 038 * <p> 039 * This check does not validate sequential blocks. This check does not violate fallthrough. 040 * </p> 041 * 042 * <p> 043 * NOTE: This check processes LITERAL_CASE and LITERAL_DEFAULT separately. 044 * Verification empty block is done for single nearest {@code case} or {@code default}. 045 * </p> 046 * <ul> 047 * <li> 048 * Property {@code option} - Specify the policy on block contents. 049 * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption}. 050 * Default value is {@code statement}. 051 * </li> 052 * <li> 053 * Property {@code tokens} - tokens to check 054 * Type is {@code java.lang.String[]}. 055 * Validation type is {@code tokenSet}. 056 * Default value is: 057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE"> 058 * LITERAL_WHILE</a>, 059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY"> 060 * LITERAL_TRY</a>, 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY"> 062 * LITERAL_FINALLY</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO"> 064 * LITERAL_DO</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF"> 066 * LITERAL_IF</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE"> 068 * LITERAL_ELSE</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR"> 070 * LITERAL_FOR</a>, 071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT"> 072 * INSTANCE_INIT</a>, 073 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 074 * STATIC_INIT</a>, 075 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH"> 076 * LITERAL_SWITCH</a>, 077 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED"> 078 * LITERAL_SYNCHRONIZED</a>. 079 * </li> 080 * </ul> 081 * 082 * <p> 083 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 084 * </p> 085 * 086 * <p> 087 * Violation Message Keys: 088 * </p> 089 * <ul> 090 * <li> 091 * {@code block.empty} 092 * </li> 093 * <li> 094 * {@code block.noStatement} 095 * </li> 096 * </ul> 097 * 098 * @since 3.0 099 */ 100@StatelessCheck 101public class EmptyBlockCheck 102 extends AbstractCheck { 103 104 /** 105 * A key is pointing to the warning message text in "messages.properties" 106 * file. 107 */ 108 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement"; 109 110 /** 111 * A key is pointing to the warning message text in "messages.properties" 112 * file. 113 */ 114 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 115 116 /** Specify the policy on block contents. */ 117 private BlockOption option = BlockOption.STATEMENT; 118 119 /** 120 * Setter to specify the policy on block contents. 121 * 122 * @param optionStr string to decode option from 123 * @throws IllegalArgumentException if unable to decode 124 * @since 3.0 125 */ 126 public void setOption(String optionStr) { 127 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 128 } 129 130 @Override 131 public int[] getDefaultTokens() { 132 return new int[] { 133 TokenTypes.LITERAL_WHILE, 134 TokenTypes.LITERAL_TRY, 135 TokenTypes.LITERAL_FINALLY, 136 TokenTypes.LITERAL_DO, 137 TokenTypes.LITERAL_IF, 138 TokenTypes.LITERAL_ELSE, 139 TokenTypes.LITERAL_FOR, 140 TokenTypes.INSTANCE_INIT, 141 TokenTypes.STATIC_INIT, 142 TokenTypes.LITERAL_SWITCH, 143 TokenTypes.LITERAL_SYNCHRONIZED, 144 }; 145 } 146 147 @Override 148 public int[] getAcceptableTokens() { 149 return new int[] { 150 TokenTypes.LITERAL_WHILE, 151 TokenTypes.LITERAL_TRY, 152 TokenTypes.LITERAL_CATCH, 153 TokenTypes.LITERAL_FINALLY, 154 TokenTypes.LITERAL_DO, 155 TokenTypes.LITERAL_IF, 156 TokenTypes.LITERAL_ELSE, 157 TokenTypes.LITERAL_FOR, 158 TokenTypes.INSTANCE_INIT, 159 TokenTypes.STATIC_INIT, 160 TokenTypes.LITERAL_SWITCH, 161 TokenTypes.LITERAL_SYNCHRONIZED, 162 TokenTypes.LITERAL_CASE, 163 TokenTypes.LITERAL_DEFAULT, 164 TokenTypes.ARRAY_INIT, 165 }; 166 } 167 168 @Override 169 public int[] getRequiredTokens() { 170 return CommonUtil.EMPTY_INT_ARRAY; 171 } 172 173 @Override 174 public void visitToken(DetailAST ast) { 175 final Optional<DetailAST> leftCurly = getLeftCurly(ast); 176 if (leftCurly.isPresent()) { 177 final DetailAST leftCurlyAST = leftCurly.orElseThrow(); 178 if (option == BlockOption.STATEMENT) { 179 final boolean emptyBlock; 180 if (leftCurlyAST.getType() == TokenTypes.LCURLY) { 181 final DetailAST nextSibling = leftCurlyAST.getNextSibling(); 182 emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP 183 && nextSibling.getType() != TokenTypes.SWITCH_RULE; 184 } 185 else { 186 emptyBlock = leftCurlyAST.getChildCount() <= 1; 187 } 188 if (emptyBlock) { 189 log(leftCurlyAST, 190 MSG_KEY_BLOCK_NO_STATEMENT); 191 } 192 } 193 else if (!hasText(leftCurlyAST)) { 194 log(leftCurlyAST, 195 MSG_KEY_BLOCK_EMPTY, 196 ast.getText()); 197 } 198 } 199 } 200 201 /** 202 * Checks if SLIST token contains any text. 203 * 204 * @param slistAST a {@code DetailAST} value 205 * @return whether the SLIST token contains any text. 206 */ 207 private boolean hasText(final DetailAST slistAST) { 208 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 209 final DetailAST rcurlyAST; 210 211 if (rightCurly == null) { 212 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 213 } 214 else { 215 rcurlyAST = rightCurly; 216 } 217 final int slistLineNo = slistAST.getLineNo(); 218 final int slistColNo = slistAST.getColumnNo(); 219 final int rcurlyLineNo = rcurlyAST.getLineNo(); 220 final int rcurlyColNo = rcurlyAST.getColumnNo(); 221 boolean returnValue = false; 222 if (slistLineNo == rcurlyLineNo) { 223 // Handle braces on the same line 224 final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1), 225 slistColNo + 1, rcurlyColNo); 226 227 if (!CodePointUtil.isBlank(txt)) { 228 returnValue = true; 229 } 230 } 231 else { 232 final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1); 233 final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine, 234 slistColNo + 1, codePointsFirstLine.length); 235 final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1); 236 final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo); 237 // check if all lines are also only whitespace 238 returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine)) 239 || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo); 240 } 241 return returnValue; 242 } 243 244 /** 245 * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive) 246 * contain whitespaces only. 247 * 248 * @param lineFrom 249 * check from this line number 250 * @param lineTo 251 * check to this line numbers 252 * @return true if lines contain only whitespaces 253 */ 254 private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) { 255 boolean result = true; 256 for (int i = lineFrom; i < lineTo - 1; i++) { 257 if (!CodePointUtil.isBlank(getLineCodePoints(i))) { 258 result = false; 259 break; 260 } 261 } 262 return result; 263 } 264 265 /** 266 * Calculates the left curly corresponding to the block to be checked. 267 * 268 * @param ast a {@code DetailAST} value 269 * @return the left curly corresponding to the block to be checked 270 */ 271 private static Optional<DetailAST> getLeftCurly(DetailAST ast) { 272 final DetailAST parent = ast.getParent(); 273 final int parentType = parent.getType(); 274 final Optional<DetailAST> leftCurly; 275 276 if (parentType == TokenTypes.SWITCH_RULE) { 277 // get left curly of a case or default that is in switch rule 278 leftCurly = Optional.ofNullable(parent.findFirstToken(TokenTypes.SLIST)); 279 } 280 else if (parentType == TokenTypes.CASE_GROUP) { 281 // get left curly of a case or default that is in switch statement 282 leftCurly = Optional.ofNullable(ast.getNextSibling()) 283 .map(DetailAST::getFirstChild) 284 .filter(node -> node.getType() == TokenTypes.SLIST); 285 } 286 else if (ast.findFirstToken(TokenTypes.SLIST) != null) { 287 // we have a left curly that is part of a statement list, but not in a case or default 288 leftCurly = Optional.of(ast.findFirstToken(TokenTypes.SLIST)); 289 } 290 else { 291 // get the first left curly that we can find, if it is present 292 leftCurly = Optional.ofNullable(ast.findFirstToken(TokenTypes.LCURLY)); 293 } 294 return leftCurly; 295 } 296 297}