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