1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.blocks; 21 22 import java.util.regex.Pattern; 23 24 import com.puppycrawl.tools.checkstyle.StatelessCheck; 25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 26 import com.puppycrawl.tools.checkstyle.api.DetailAST; 27 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 28 29 /** 30 * <div> 31 * Checks for empty catch blocks. 32 * By default, check allows empty catch block with any comment inside. 33 * </div> 34 * 35 * <p> 36 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 37 * <b>commentFormat</b>. 38 * If both options are specified - they are applied by <b>any of them is matching</b>. 39 * </p> 40 * <ul> 41 * <li> 42 * Property {@code commentFormat} - Specify the RegExp for the first comment inside empty 43 * catch block. If check meets comment inside empty catch block matching specified format 44 * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed. 45 * Type is {@code java.util.regex.Pattern}. 46 * Default value is {@code ".*"}. 47 * </li> 48 * <li> 49 * Property {@code exceptionVariableName} - Specify the RegExp for the name of the variable 50 * associated with exception. If check meets variable name matching specified value - empty 51 * block is suppressed. 52 * Type is {@code java.util.regex.Pattern}. 53 * Default value is {@code "^$"}. 54 * </li> 55 * </ul> 56 * 57 * <p> 58 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 59 * </p> 60 * 61 * <p> 62 * Violation Message Keys: 63 * </p> 64 * <ul> 65 * <li> 66 * {@code catch.block.empty} 67 * </li> 68 * </ul> 69 * 70 * @since 6.4 71 */ 72 @StatelessCheck 73 public class EmptyCatchBlockCheck extends AbstractCheck { 74 75 /** 76 * A key is pointing to the warning message text in "messages.properties" 77 * file. 78 */ 79 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 80 81 /** 82 * A pattern to split on line ends. 83 */ 84 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r"); 85 86 /** 87 * Specify the RegExp for the name of the variable associated with exception. 88 * If check meets variable name matching specified value - empty block is suppressed. 89 */ 90 private Pattern exceptionVariableName = Pattern.compile("^$"); 91 92 /** 93 * Specify the RegExp for the first comment inside empty catch block. 94 * If check meets comment inside empty catch block matching specified format - empty 95 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 96 */ 97 private Pattern commentFormat = Pattern.compile(".*"); 98 99 /** 100 * Setter to specify the RegExp for the name of the variable associated with exception. 101 * If check meets variable name matching specified value - empty block is suppressed. 102 * 103 * @param exceptionVariablePattern 104 * pattern of exception's variable name. 105 * @since 6.4 106 */ 107 public void setExceptionVariableName(Pattern exceptionVariablePattern) { 108 exceptionVariableName = exceptionVariablePattern; 109 } 110 111 /** 112 * Setter to specify the RegExp for the first comment inside empty catch block. 113 * If check meets comment inside empty catch block matching specified format - empty 114 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 115 * 116 * @param commentPattern 117 * pattern of comment. 118 * @since 6.4 119 */ 120 public void setCommentFormat(Pattern commentPattern) { 121 commentFormat = commentPattern; 122 } 123 124 @Override 125 public int[] getDefaultTokens() { 126 return getRequiredTokens(); 127 } 128 129 @Override 130 public int[] getAcceptableTokens() { 131 return getRequiredTokens(); 132 } 133 134 @Override 135 public int[] getRequiredTokens() { 136 return new int[] { 137 TokenTypes.LITERAL_CATCH, 138 }; 139 } 140 141 @Override 142 public boolean isCommentNodesRequired() { 143 return true; 144 } 145 146 @Override 147 public void visitToken(DetailAST ast) { 148 visitCatchBlock(ast); 149 } 150 151 /** 152 * Visits catch ast node, if it is empty catch block - checks it according to 153 * Check's options. If exception's variable name or comment inside block are matching 154 * specified regexp - skips from consideration, else - puts violation. 155 * 156 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 157 */ 158 private void visitCatchBlock(DetailAST catchAst) { 159 if (isEmptyCatchBlock(catchAst)) { 160 final String commentContent = getCommentFirstLine(catchAst); 161 if (isVerifiable(catchAst, commentContent)) { 162 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY); 163 } 164 } 165 } 166 167 /** 168 * Gets the first line of comment in catch block. If comment is single-line - 169 * returns it fully, else if comment is multi-line - returns the first line. 170 * 171 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 172 * @return the first line of comment in catch block, "" if no comment was found. 173 */ 174 private static String getCommentFirstLine(DetailAST catchAst) { 175 final DetailAST slistToken = catchAst.getLastChild(); 176 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 177 String commentContent = ""; 178 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 179 commentContent = firstElementInBlock.getFirstChild().getText(); 180 } 181 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 182 commentContent = firstElementInBlock.getFirstChild().getText(); 183 final String[] lines = LINE_END_PATTERN.split(commentContent); 184 for (String line : lines) { 185 if (!line.isEmpty()) { 186 commentContent = line; 187 break; 188 } 189 } 190 } 191 return commentContent; 192 } 193 194 /** 195 * Checks if current empty catch block is verifiable according to Check's options 196 * (exception's variable name and comment format are both in consideration). 197 * 198 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 199 * @param commentContent text of comment. 200 * @return true if empty catch block is verifiable by Check. 201 */ 202 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 203 final String variableName = getExceptionVariableName(emptyCatchAst); 204 final boolean isMatchingVariableName = exceptionVariableName 205 .matcher(variableName).find(); 206 final boolean isMatchingCommentContent = !commentContent.isEmpty() 207 && commentFormat.matcher(commentContent).find(); 208 return !isMatchingVariableName && !isMatchingCommentContent; 209 } 210 211 /** 212 * Checks if catch block is empty or contains only comments. 213 * 214 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 215 * @return true if catch block is empty. 216 */ 217 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 218 boolean result = true; 219 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 220 DetailAST catchBlockStmt = slistToken.getFirstChild(); 221 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 222 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 223 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 224 result = false; 225 break; 226 } 227 catchBlockStmt = catchBlockStmt.getNextSibling(); 228 } 229 return result; 230 } 231 232 /** 233 * Gets variable's name associated with exception. 234 * 235 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 236 * @return Variable's name associated with exception. 237 */ 238 private static String getExceptionVariableName(DetailAST catchAst) { 239 final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF); 240 final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT); 241 return variableName.getText(); 242 } 243 244 }