001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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 * Checks for empty blocks. This check does not validate sequential blocks. 032 * The policy to verify is specified using the {@link 033 * BlockOption} class and defaults to {@link BlockOption#STATEMENT}. 034 * 035 * <p> By default the check will check the following blocks: 036 * {@link TokenTypes#LITERAL_WHILE LITERAL_WHILE}, 037 * {@link TokenTypes#LITERAL_TRY LITERAL_TRY}, 038 * {@link TokenTypes#LITERAL_FINALLY LITERAL_FINALLY}, 039 * {@link TokenTypes#LITERAL_DO LITERAL_DO}, 040 * {@link TokenTypes#LITERAL_IF LITERAL_IF}, 041 * {@link TokenTypes#LITERAL_ELSE LITERAL_ELSE}, 042 * {@link TokenTypes#LITERAL_FOR LITERAL_FOR}, 043 * {@link TokenTypes#STATIC_INIT STATIC_INIT}, 044 * {@link TokenTypes#LITERAL_SWITCH LITERAL_SWITCH}. 045 * {@link TokenTypes#LITERAL_SYNCHRONIZED LITERAL_SYNCHRONIZED}. 046 * </p> 047 * 048 * <p> An example of how to configure the check is: 049 * </p> 050 * <pre> 051 * <module name="EmptyBlock"/> 052 * </pre> 053 * 054 * <p> An example of how to configure the check for the {@link 055 * BlockOption#TEXT} policy and only try blocks is: 056 * </p> 057 * 058 * <pre> 059 * <module name="EmptyBlock"> 060 * <property name="tokens" value="LITERAL_TRY"/> 061 * <property name="option" value="text"/> 062 * </module> 063 * </pre> 064 * 065 */ 066@StatelessCheck 067public class EmptyBlockCheck 068 extends AbstractCheck { 069 070 /** 071 * A key is pointing to the warning message text in "messages.properties" 072 * file. 073 */ 074 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement"; 075 076 /** 077 * A key is pointing to the warning message text in "messages.properties" 078 * file. 079 */ 080 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty"; 081 082 /** The policy to enforce. */ 083 private BlockOption option = BlockOption.STATEMENT; 084 085 /** 086 * Set the option to enforce. 087 * @param optionStr string to decode option from 088 * @throws IllegalArgumentException if unable to decode 089 */ 090 public void setOption(String optionStr) { 091 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH)); 092 } 093 094 @Override 095 public int[] getDefaultTokens() { 096 return new int[] { 097 TokenTypes.LITERAL_WHILE, 098 TokenTypes.LITERAL_TRY, 099 TokenTypes.LITERAL_FINALLY, 100 TokenTypes.LITERAL_DO, 101 TokenTypes.LITERAL_IF, 102 TokenTypes.LITERAL_ELSE, 103 TokenTypes.LITERAL_FOR, 104 TokenTypes.INSTANCE_INIT, 105 TokenTypes.STATIC_INIT, 106 TokenTypes.LITERAL_SWITCH, 107 TokenTypes.LITERAL_SYNCHRONIZED, 108 }; 109 } 110 111 @Override 112 public int[] getAcceptableTokens() { 113 return new int[] { 114 TokenTypes.LITERAL_WHILE, 115 TokenTypes.LITERAL_TRY, 116 TokenTypes.LITERAL_CATCH, 117 TokenTypes.LITERAL_FINALLY, 118 TokenTypes.LITERAL_DO, 119 TokenTypes.LITERAL_IF, 120 TokenTypes.LITERAL_ELSE, 121 TokenTypes.LITERAL_FOR, 122 TokenTypes.INSTANCE_INIT, 123 TokenTypes.STATIC_INIT, 124 TokenTypes.LITERAL_SWITCH, 125 TokenTypes.LITERAL_SYNCHRONIZED, 126 TokenTypes.LITERAL_CASE, 127 TokenTypes.LITERAL_DEFAULT, 128 TokenTypes.ARRAY_INIT, 129 }; 130 } 131 132 @Override 133 public int[] getRequiredTokens() { 134 return CommonUtil.EMPTY_INT_ARRAY; 135 } 136 137 @Override 138 public void visitToken(DetailAST ast) { 139 final DetailAST leftCurly = findLeftCurly(ast); 140 if (leftCurly != null) { 141 if (option == BlockOption.STATEMENT) { 142 final boolean emptyBlock; 143 if (leftCurly.getType() == TokenTypes.LCURLY) { 144 emptyBlock = leftCurly.getNextSibling().getType() != TokenTypes.CASE_GROUP; 145 } 146 else { 147 emptyBlock = leftCurly.getChildCount() <= 1; 148 } 149 if (emptyBlock) { 150 log(leftCurly, 151 MSG_KEY_BLOCK_NO_STATEMENT, 152 ast.getText()); 153 } 154 } 155 else if (!hasText(leftCurly)) { 156 log(leftCurly, 157 MSG_KEY_BLOCK_EMPTY, 158 ast.getText()); 159 } 160 } 161 } 162 163 /** 164 * Checks if SLIST token contains any text. 165 * @param slistAST a {@code DetailAST} value 166 * @return whether the SLIST token contains any text. 167 */ 168 private boolean hasText(final DetailAST slistAST) { 169 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY); 170 final DetailAST rcurlyAST; 171 172 if (rightCurly == null) { 173 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY); 174 } 175 else { 176 rcurlyAST = rightCurly; 177 } 178 final int slistLineNo = slistAST.getLineNo(); 179 final int slistColNo = slistAST.getColumnNo(); 180 final int rcurlyLineNo = rcurlyAST.getLineNo(); 181 final int rcurlyColNo = rcurlyAST.getColumnNo(); 182 final String[] lines = getLines(); 183 boolean returnValue = false; 184 if (slistLineNo == rcurlyLineNo) { 185 // Handle braces on the same line 186 final String txt = lines[slistLineNo - 1] 187 .substring(slistColNo + 1, rcurlyColNo); 188 if (!CommonUtil.isBlank(txt)) { 189 returnValue = true; 190 } 191 } 192 else { 193 final String firstLine = lines[slistLineNo - 1].substring(slistColNo + 1); 194 final String lastLine = lines[rcurlyLineNo - 1].substring(0, rcurlyColNo); 195 // check if all lines are also only whitespace 196 returnValue = !(CommonUtil.isBlank(firstLine) && CommonUtil.isBlank(lastLine)) 197 || !checkIsAllLinesAreWhitespace(lines, slistLineNo, rcurlyLineNo); 198 } 199 return returnValue; 200 } 201 202 /** 203 * Checks is all lines in array contain whitespaces only. 204 * 205 * @param lines 206 * array of lines 207 * @param lineFrom 208 * check from this line number 209 * @param lineTo 210 * check to this line numbers 211 * @return true if lines contain only whitespaces 212 */ 213 private static boolean checkIsAllLinesAreWhitespace(String[] lines, int lineFrom, int lineTo) { 214 boolean result = true; 215 for (int i = lineFrom; i < lineTo - 1; i++) { 216 if (!CommonUtil.isBlank(lines[i])) { 217 result = false; 218 break; 219 } 220 } 221 return result; 222 } 223 224 /** 225 * Calculates the left curly corresponding to the block to be checked. 226 * 227 * @param ast a {@code DetailAST} value 228 * @return the left curly corresponding to the block to be checked 229 */ 230 private static DetailAST findLeftCurly(DetailAST ast) { 231 final DetailAST leftCurly; 232 final DetailAST slistAST = ast.findFirstToken(TokenTypes.SLIST); 233 if ((ast.getType() == TokenTypes.LITERAL_CASE 234 || ast.getType() == TokenTypes.LITERAL_DEFAULT) 235 && ast.getNextSibling() != null 236 && ast.getNextSibling().getFirstChild() != null 237 && ast.getNextSibling().getFirstChild().getType() == TokenTypes.SLIST) { 238 leftCurly = ast.getNextSibling().getFirstChild(); 239 } 240 else if (slistAST == null) { 241 leftCurly = ast.findFirstToken(TokenTypes.LCURLY); 242 } 243 else { 244 leftCurly = slistAST; 245 } 246 return leftCurly; 247 } 248 249}