001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * 047 * @since 3.0 048 */ 049@StatelessCheck 050public class EmptyBlockCheck 051 extends AbstractCheck { 052 053 /** 054 * A key is pointing to the warning message text in "messages.properties" 055 * file. 056 */ 057 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement"; 058 059 /** 060 * A key is pointing to the warning message text in "messages.properties" 061 * file. 062 */ 063 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 064 065 /** Specify the policy on block contents. */ 066 private BlockOption option = BlockOption.STATEMENT; 067 068 /** 069 * Creates a new {@code EmptyBlockCheck} instance. 070 */ 071 public EmptyBlockCheck() { 072 // no code by default 073 } 074 075 /** 076 * Setter to specify the policy on block contents. 077 * 078 * @param optionStr string to decode option from 079 * @throws IllegalArgumentException if unable to decode 080 * @since 3.0 081 */ 082 public void setOption(String optionStr) { 083 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 084 } 085 086 @Override 087 public int[] getDefaultTokens() { 088 return new int[] { 089 TokenTypes.LITERAL_WHILE, 090 TokenTypes.LITERAL_TRY, 091 TokenTypes.LITERAL_FINALLY, 092 TokenTypes.LITERAL_DO, 093 TokenTypes.LITERAL_IF, 094 TokenTypes.LITERAL_ELSE, 095 TokenTypes.LITERAL_FOR, 096 TokenTypes.INSTANCE_INIT, 097 TokenTypes.STATIC_INIT, 098 TokenTypes.LITERAL_SWITCH, 099 TokenTypes.LITERAL_SYNCHRONIZED, 100 }; 101 } 102 103 @Override 104 public int[] getAcceptableTokens() { 105 return new int[] { 106 TokenTypes.LITERAL_WHILE, 107 TokenTypes.LITERAL_TRY, 108 TokenTypes.LITERAL_CATCH, 109 TokenTypes.LITERAL_FINALLY, 110 TokenTypes.LITERAL_DO, 111 TokenTypes.LITERAL_IF, 112 TokenTypes.LITERAL_ELSE, 113 TokenTypes.LITERAL_FOR, 114 TokenTypes.INSTANCE_INIT, 115 TokenTypes.STATIC_INIT, 116 TokenTypes.LITERAL_SWITCH, 117 TokenTypes.LITERAL_SYNCHRONIZED, 118 TokenTypes.LITERAL_CASE, 119 TokenTypes.LITERAL_DEFAULT, 120 TokenTypes.ARRAY_INIT, 121 }; 122 } 123 124 @Override 125 public int[] getRequiredTokens() { 126 return CommonUtil.EMPTY_INT_ARRAY; 127 } 128 129 @Override 130 public void visitToken(DetailAST ast) { 131 final Optional<DetailAST> leftCurly = getLeftCurly(ast); 132 if (leftCurly.isPresent()) { 133 final DetailAST leftCurlyAST = leftCurly.orElseThrow(); 134 if (option == BlockOption.STATEMENT) { 135 final boolean emptyBlock; 136 if (leftCurlyAST.getType() == TokenTypes.LCURLY) { 137 final DetailAST nextSibling = leftCurlyAST.getNextSibling(); 138 emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP 139 && nextSibling.getType() != TokenTypes.SWITCH_RULE; 140 } 141 else { 142 emptyBlock = leftCurlyAST.getChildCount() <= 1; 143 } 144 if (emptyBlock) { 145 log(leftCurlyAST, 146 MSG_KEY_BLOCK_NO_STATEMENT); 147 } 148 } 149 else if (!hasText(leftCurlyAST)) { 150 log(leftCurlyAST, 151 MSG_KEY_BLOCK_EMPTY, 152 ast.getText()); 153 } 154 } 155 } 156 157 /** 158 * Checks if SLIST token contains any text. 159 * 160 * @param slistAST a {@code DetailAST} value 161 * @return whether the SLIST token contains any text. 162 */ 163 private boolean hasText(final DetailAST slistAST) { 164 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 165 final DetailAST rcurlyAST; 166 167 if (rightCurly == null) { 168 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 169 } 170 else { 171 rcurlyAST = rightCurly; 172 } 173 final int slistLineNo = slistAST.getLineNo(); 174 final int slistColNo = slistAST.getColumnNo(); 175 final int rcurlyLineNo = rcurlyAST.getLineNo(); 176 final int rcurlyColNo = rcurlyAST.getColumnNo(); 177 boolean returnValue = false; 178 if (slistLineNo == rcurlyLineNo) { 179 // Handle braces on the same line 180 final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1), 181 slistColNo + 1, rcurlyColNo); 182 183 if (!CodePointUtil.isBlank(txt)) { 184 returnValue = true; 185 } 186 } 187 else { 188 final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1); 189 final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine, 190 slistColNo + 1, codePointsFirstLine.length); 191 final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1); 192 final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo); 193 // check if all lines are also only whitespace 194 returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine)) 195 || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo); 196 } 197 return returnValue; 198 } 199 200 /** 201 * Checks is all lines from 'lineFrom' to 'lineTo' (exclusive) 202 * contain whitespaces only. 203 * 204 * @param lineFrom 205 * check from this line number 206 * @param lineTo 207 * check to this line numbers 208 * @return true if lines contain only whitespaces 209 */ 210 private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) { 211 boolean result = true; 212 for (int i = lineFrom; i < lineTo - 1; i++) { 213 if (!CodePointUtil.isBlank(getLineCodePoints(i))) { 214 result = false; 215 break; 216 } 217 } 218 return result; 219 } 220 221 /** 222 * Calculates the left curly corresponding to the block to be checked. 223 * 224 * @param ast a {@code DetailAST} value 225 * @return the left curly corresponding to the block to be checked 226 */ 227 private static Optional<DetailAST> getLeftCurly(DetailAST ast) { 228 final DetailAST parent = ast.getParent(); 229 final int parentType = parent.getType(); 230 final Optional<DetailAST> leftCurly; 231 232 if (parentType == TokenTypes.SWITCH_RULE) { 233 // get left curly of a case or default that is in switch rule 234 leftCurly = Optional.ofNullable(parent.findFirstToken(TokenTypes.SLIST)); 235 } 236 else if (parentType == TokenTypes.CASE_GROUP) { 237 // get left curly of a case or default that is in switch statement 238 leftCurly = Optional.ofNullable(ast.getNextSibling()) 239 .map(DetailAST::getFirstChild) 240 .filter(node -> node.getType() == TokenTypes.SLIST); 241 } 242 else if (ast.findFirstToken(TokenTypes.SLIST) != null) { 243 // we have a left curly that is part of a statement list, but not in a case or default 244 leftCurly = Optional.of(ast.findFirstToken(TokenTypes.SLIST)); 245 } 246 else { 247 // get the first left curly that we can find, if it is present 248 leftCurly = Optional.ofNullable(ast.findFirstToken(TokenTypes.LCURLY)); 249 } 250 return leftCurly; 251 } 252 253}