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