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