001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * Notes: 037 * There are two options to make validation more precise: <b>exceptionVariableName</b> and 038 * <b>commentFormat</b>. 039 * If both options are specified - they are applied by <b>any of them is matching</b>. 040 * </p> 041 * 042 * @since 6.4 043 */ 044@StatelessCheck 045public class EmptyCatchBlockCheck extends AbstractCheck { 046 047 /** 048 * A key is pointing to the warning message text in "messages.properties" 049 * file. 050 */ 051 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty"; 052 053 /** 054 * A pattern to split on line ends. 055 */ 056 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r"); 057 058 /** 059 * Specify the RegExp for the name of the variable associated with exception. 060 * If check meets variable name matching specified value - empty block is suppressed. 061 */ 062 private Pattern exceptionVariableName = Pattern.compile("^$"); 063 064 /** 065 * Specify the RegExp for the first comment inside empty catch block. 066 * If check meets comment inside empty catch block matching specified format - empty 067 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 068 */ 069 private Pattern commentFormat = Pattern.compile(".*"); 070 071 /** 072 * Setter to specify the RegExp for the name of the variable associated with exception. 073 * If check meets variable name matching specified value - empty block is suppressed. 074 * 075 * @param exceptionVariablePattern 076 * pattern of exception's variable name. 077 * @since 6.4 078 */ 079 public void setExceptionVariableName(Pattern exceptionVariablePattern) { 080 exceptionVariableName = exceptionVariablePattern; 081 } 082 083 /** 084 * Setter to specify the RegExp for the first comment inside empty catch block. 085 * If check meets comment inside empty catch block matching specified format - empty 086 * block is suppressed. If it is multi-line comment - only its first line is analyzed. 087 * 088 * @param commentPattern 089 * pattern of comment. 090 * @since 6.4 091 */ 092 public void setCommentFormat(Pattern commentPattern) { 093 commentFormat = commentPattern; 094 } 095 096 @Override 097 public int[] getDefaultTokens() { 098 return getRequiredTokens(); 099 } 100 101 @Override 102 public int[] getAcceptableTokens() { 103 return getRequiredTokens(); 104 } 105 106 @Override 107 public int[] getRequiredTokens() { 108 return new int[] { 109 TokenTypes.LITERAL_CATCH, 110 }; 111 } 112 113 @Override 114 public boolean isCommentNodesRequired() { 115 return true; 116 } 117 118 @Override 119 public void visitToken(DetailAST ast) { 120 visitCatchBlock(ast); 121 } 122 123 /** 124 * Visits catch ast node, if it is empty catch block - checks it according to 125 * Check's options. If exception's variable name or comment inside block are matching 126 * specified regexp - skips from consideration, else - puts violation. 127 * 128 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 129 */ 130 private void visitCatchBlock(DetailAST catchAst) { 131 if (isEmptyCatchBlock(catchAst)) { 132 final String commentContent = getCommentFirstLine(catchAst); 133 if (isVerifiable(catchAst, commentContent)) { 134 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY); 135 } 136 } 137 } 138 139 /** 140 * Gets the first line of comment in catch block. If comment is single-line - 141 * returns it fully, else if comment is multi-line - returns the first line. 142 * 143 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 144 * @return the first line of comment in catch block, "" if no comment was found. 145 */ 146 private static String getCommentFirstLine(DetailAST catchAst) { 147 final DetailAST slistToken = catchAst.getLastChild(); 148 final DetailAST firstElementInBlock = slistToken.getFirstChild(); 149 String commentContent = ""; 150 if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 151 commentContent = firstElementInBlock.getFirstChild().getText(); 152 } 153 else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 154 commentContent = firstElementInBlock.getFirstChild().getText(); 155 final String[] lines = LINE_END_PATTERN.split(commentContent); 156 for (String line : lines) { 157 if (!line.isEmpty()) { 158 commentContent = line; 159 break; 160 } 161 } 162 } 163 return commentContent; 164 } 165 166 /** 167 * Checks if current empty catch block is verifiable according to Check's options 168 * (exception's variable name and comment format are both in consideration). 169 * 170 * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block. 171 * @param commentContent text of comment. 172 * @return true if empty catch block is verifiable by Check. 173 */ 174 private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) { 175 final String variableName = getExceptionVariableName(emptyCatchAst); 176 final boolean isMatchingVariableName = exceptionVariableName 177 .matcher(variableName).find(); 178 final boolean isMatchingCommentContent = !commentContent.isEmpty() 179 && commentFormat.matcher(commentContent).find(); 180 return !isMatchingVariableName && !isMatchingCommentContent; 181 } 182 183 /** 184 * Checks if catch block is empty or contains only comments. 185 * 186 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 187 * @return true if catch block is empty. 188 */ 189 private static boolean isEmptyCatchBlock(DetailAST catchAst) { 190 boolean result = true; 191 final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST); 192 DetailAST catchBlockStmt = slistToken.getFirstChild(); 193 while (catchBlockStmt.getType() != TokenTypes.RCURLY) { 194 if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT 195 && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 196 result = false; 197 break; 198 } 199 catchBlockStmt = catchBlockStmt.getNextSibling(); 200 } 201 return result; 202 } 203 204 /** 205 * Gets variable's name associated with exception. 206 * 207 * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} 208 * @return Variable's name associated with exception. 209 */ 210 private static String getExceptionVariableName(DetailAST catchAst) { 211 final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF); 212 final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT); 213 return variableName.getText(); 214 } 215 216}