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 * <p> 31 * Checks for empty catch blocks. 32 * By default, check allows empty catch block with any comment inside. 33 * </p> 34 * <p> 35 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 36 * <b>commentFormat</b>. 37 * If both options are specified - they are applied by <b>any of them is matching</b>. 38 * </p> 39 * <ul> 40 * <li> 41 * Property {@code commentFormat} - Specify the RegExp for the first comment inside empty 42 * catch block. If check meets comment inside empty catch block matching specified format 43 * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed. 44 * Type is {@code java.util.regex.Pattern}. 45 * Default value is {@code ".*"}. 46 * </li> 47 * <li> 48 * Property {@code exceptionVariableName} - Specify the RegExp for the name of the variable 49 * associated with exception. If check meets variable name matching specified value - empty 50 * block is suppressed. 51 * Type is {@code java.util.regex.Pattern}. 52 * Default value is {@code "^$"}. 53 * </li> 54 * </ul> 55 * <p> 56 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 57 * </p> 58 * <p> 59 * Violation Message Keys: 60 * </p> 61 * <ul> 62 * <li> 63 * {@code catch.block.empty} 64 * </li> 65 * </ul> 66 * 67 * @since 6.4 68 */ 69 @StatelessCheck 70 public class EmptyCatchBlockCheck extends AbstractCheck { 71 72 /** 73 * A key is pointing to the warning message text in "messages.properties" 74 * file. 75 */ 76 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 77 78 /** 79 * A pattern to split on line ends. 80 */ 81 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r"); 82 83 /** 84 * Specify the RegExp for the name of the variable associated with exception. 85 * If check meets variable name matching specified value - empty block is suppressed. 86 */ 87 private Pattern exceptionVariableName = Pattern.compile("^$"); 88 89 /** 90 * Specify the RegExp for the first comment inside empty catch block. 91 * If check meets comment inside empty catch block matching specified format - empty 92 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 93 */ 94 private Pattern commentFormat = Pattern.compile(".*"); 95 96 /** 97 * Setter to specify the RegExp for the name of the variable associated with exception. 98 * If check meets variable name matching specified value - empty block is suppressed. 99 * 100 * @param exceptionVariablePattern 101 * pattern of exception's variable name. 102 * @since 6.4 103 */ 104 public void setExceptionVariableName(Pattern exceptionVariablePattern) { 105 exceptionVariableName = exceptionVariablePattern; 106 } 107 108 /** 109 * Setter to specify the RegExp for the first comment inside empty catch block. 110 * If check meets comment inside empty catch block matching specified format - empty 111 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 112 * 113 * @param commentPattern 114 * pattern of comment. 115 * @since 6.4 116 */ 117 public void setCommentFormat(Pattern commentPattern) { 118 commentFormat = commentPattern; 119 } 120 121 @Override 122 public int[] getDefaultTokens() { 123 return getRequiredTokens(); 124 } 125 126 @Override 127 public int[] getAcceptableTokens() { 128 return getRequiredTokens(); 129 } 130 131 @Override 132 public int[] getRequiredTokens() { 133 return new int[] { 134 TokenTypes.LITERAL_CATCH, 135 }; 136 } 137 138 @Override 139 public boolean isCommentNodesRequired() { 140 return true; 141 } 142 143 @Override 144 public void visitToken(DetailAST ast) { 145 visitCatchBlock(ast); 146 } 147 148 /** 149 * Visits catch ast node, if it is empty catch block - checks it according to 150 * Check's options. If exception's variable name or comment inside block are matching 151 * specified regexp - skips from consideration, else - puts violation. 152 * 153 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 154 */ 155 private void visitCatchBlock(DetailAST catchAst) { 156 if (isEmptyCatchBlock(catchAst)) { 157 final String commentContent = getCommentFirstLine(catchAst); 158 if (isVerifiable(catchAst, commentContent)) { 159 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY); 160 } 161 } 162 } 163 164 /** 165 * Gets the first line of comment in catch block. If comment is single-line - 166 * returns it fully, else if comment is multi-line - returns the first line. 167 * 168 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 169 * @return the first line of comment in catch block, "" if no comment was found. 170 */ 171 private static String getCommentFirstLine(DetailAST catchAst) { 172 final DetailAST slistToken = catchAst.getLastChild(); 173 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 174 String commentContent = ""; 175 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 176 commentContent = firstElementInBlock.getFirstChild().getText(); 177 } 178 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 179 commentContent = firstElementInBlock.getFirstChild().getText(); 180 final String[] lines = LINE_END_PATTERN.split(commentContent); 181 for (String line : lines) { 182 if (!line.isEmpty()) { 183 commentContent = line; 184 break; 185 } 186 } 187 } 188 return commentContent; 189 } 190 191 /** 192 * Checks if current empty catch block is verifiable according to Check's options 193 * (exception's variable name and comment format are both in consideration). 194 * 195 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 196 * @param commentContent text of comment. 197 * @return true if empty catch block is verifiable by Check. 198 */ 199 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 200 final String variableName = getExceptionVariableName(emptyCatchAst); 201 final boolean isMatchingVariableName = exceptionVariableName 202 .matcher(variableName).find(); 203 final boolean isMatchingCommentContent = !commentContent.isEmpty() 204 && commentFormat.matcher(commentContent).find(); 205 return !isMatchingVariableName && !isMatchingCommentContent; 206 } 207 208 /** 209 * Checks if catch block is empty or contains only comments. 210 * 211 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 212 * @return true if catch block is empty. 213 */ 214 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 215 boolean result = true; 216 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 217 DetailAST catchBlockStmt = slistToken.getFirstChild(); 218 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 219 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 220 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 221 result = false; 222 break; 223 } 224 catchBlockStmt = catchBlockStmt.getNextSibling(); 225 } 226 return result; 227 } 228 229 /** 230 * Gets variable's name associated with exception. 231 * 232 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 233 * @return Variable's name associated with exception. 234 */ 235 private static String getExceptionVariableName(DetailAST catchAst) { 236 final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF); 237 final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT); 238 return variableName.getText(); 239 } 240 241 }