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