001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.regex.Pattern; 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; 028 029/** 030 * <div> 031 * Checks for empty catch blocks. 032 * By default, check allows empty catch block with any comment inside. 033 * </div> 034 * 035 * <p> 036 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 037 * <b>commentFormat</b>. 038 * If both options are specified - they are applied by <b>any of them is matching</b>. 039 * </p> 040 * <ul> 041 * <li> 042 * Property {@code commentFormat} - Specify the RegExp for the first comment inside empty 043 * catch block. If check meets comment inside empty catch block matching specified format 044 * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed. 045 * Type is {@code java.util.regex.Pattern}. 046 * Default value is {@code ".*"}. 047 * </li> 048 * <li> 049 * Property {@code exceptionVariableName} - Specify the RegExp for the name of the variable 050 * associated with exception. If check meets variable name matching specified value - empty 051 * block is suppressed. 052 * Type is {@code java.util.regex.Pattern}. 053 * Default value is {@code "^$"}. 054 * </li> 055 * </ul> 056 * 057 * <p> 058 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 059 * </p> 060 * 061 * <p> 062 * Violation Message Keys: 063 * </p> 064 * <ul> 065 * <li> 066 * {@code catch.block.empty} 067 * </li> 068 * </ul> 069 * 070 * @since 6.4 071 */ 072@StatelessCheck 073public class EmptyCatchBlockCheck extends AbstractCheck { 074 075 /** 076 * A key is pointing to the warning message text in "messages.properties" 077 * file. 078 */ 079 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 080 081 /** 082 * A pattern to split on line ends. 083 */ 084 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r"); 085 086 /** 087 * Specify the RegExp for the name of the variable associated with exception. 088 * If check meets variable name matching specified value - empty block is suppressed. 089 */ 090 private Pattern exceptionVariableName = Pattern.compile("^$"); 091 092 /** 093 * Specify the RegExp for the first comment inside empty catch block. 094 * If check meets comment inside empty catch block matching specified format - empty 095 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 096 */ 097 private Pattern commentFormat = Pattern.compile(".*"); 098 099 /** 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}