1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
29 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30
31 /**
32 * <div>
33 * Checks for empty catch blocks.
34 * By default, check allows empty catch block with any comment inside.
35 * </div>
36 *
37 * <p>
38 * Notes:
39 * There are two options to make validation more precise: <b>exceptionVariableName</b> and
40 * <b>commentFormat</b>.
41 * If both options are specified - they are applied by <b>any of them is matching</b>.
42 * </p>
43 *
44 * @since 6.4
45 */
46 @StatelessCheck
47 public class EmptyCatchBlockCheck extends AbstractCheck {
48
49 /**
50 * A key is pointing to the warning message text in "messages.properties"
51 * file.
52 */
53 public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty";
54
55 /**
56 * A pattern to split on line ends.
57 */
58 private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r");
59
60 /**
61 * Specify the RegExp for the name of the variable associated with exception.
62 * If check meets variable name matching specified value - empty block is suppressed.
63 */
64 private Pattern exceptionVariableName = Pattern.compile("^$");
65
66 /**
67 * Specify the RegExp for the first comment inside empty catch block.
68 * If check meets comment inside empty catch block matching specified format - empty
69 * block is suppressed. If it is multi-line comment - only its first line is analyzed.
70 */
71 private Pattern commentFormat = Pattern.compile(".*");
72
73 /**
74 * Creates a new {@code EmptyCatchBlockCheck} instance.
75 */
76 public EmptyCatchBlockCheck() {
77 // no code by default
78 }
79
80 /**
81 * Setter to specify the RegExp for the name of the variable associated with exception.
82 * If check meets variable name matching specified value - empty block is suppressed.
83 *
84 * @param exceptionVariablePattern
85 * pattern of exception's variable name.
86 * @since 6.4
87 */
88 public void setExceptionVariableName(Pattern exceptionVariablePattern) {
89 exceptionVariableName = exceptionVariablePattern;
90 }
91
92 /**
93 * Setter to 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 * @param commentPattern
98 * pattern of comment.
99 * @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 }