View Javadoc
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   * <ul>
42   * <li>
43   * Property {@code commentFormat} - Specify the RegExp for the first comment inside empty
44   * catch block. If check meets comment inside empty catch block matching specified format
45   * - empty block is suppressed. If it is multi-line comment - only its first line is analyzed.
46   * Type is {@code java.util.regex.Pattern}.
47   * Default value is {@code ".*"}.
48   * </li>
49   * <li>
50   * Property {@code exceptionVariableName} - Specify the RegExp for the name of the variable
51   * associated with exception. If check meets variable name matching specified value - empty
52   * block is suppressed.
53   * Type is {@code java.util.regex.Pattern}.
54   * Default value is {@code "^$"}.
55   * </li>
56   * </ul>
57   *
58   * <p>
59   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
60   * </p>
61   *
62   * <p>
63   * Violation Message Keys:
64   * </p>
65   * <ul>
66   * <li>
67   * {@code catch.block.empty}
68   * </li>
69   * </ul>
70   *
71   * @since 6.4
72   */
73  @StatelessCheck
74  public class EmptyCatchBlockCheck extends AbstractCheck {
75  
76      /**
77       * A key is pointing to the warning message text in "messages.properties"
78       * file.
79       */
80      public static final String MSG_KEY_CATCH_BLOCK_EMPTY = "catch.block.empty";
81  
82      /**
83       * A pattern to split on line ends.
84       */
85      private static final Pattern LINE_END_PATTERN = Pattern.compile("\\r?+\\n|\\r");
86  
87      /**
88       * Specify the RegExp for the name of the variable associated with exception.
89       * If check meets variable name matching specified value - empty block is suppressed.
90       */
91      private Pattern exceptionVariableName = Pattern.compile("^$");
92  
93      /**
94       * Specify the RegExp for the first comment inside empty catch block.
95       * If check meets comment inside empty catch block matching specified format - empty
96       * block is suppressed. If it is multi-line comment - only its first line is analyzed.
97       */
98      private Pattern commentFormat = Pattern.compile(".*");
99  
100     /**
101      * Setter to specify the RegExp for the name of the variable associated with exception.
102      * If check meets variable name matching specified value - empty block is suppressed.
103      *
104      * @param exceptionVariablePattern
105      *        pattern of exception's variable name.
106      * @since 6.4
107      */
108     public void setExceptionVariableName(Pattern exceptionVariablePattern) {
109         exceptionVariableName = exceptionVariablePattern;
110     }
111 
112     /**
113      * Setter to specify the RegExp for the first comment inside empty catch block.
114      * If check meets comment inside empty catch block matching specified format - empty
115      * block is suppressed. If it is multi-line comment - only its first line is analyzed.
116      *
117      * @param commentPattern
118      *        pattern of comment.
119      * @since 6.4
120      */
121     public void setCommentFormat(Pattern commentPattern) {
122         commentFormat = commentPattern;
123     }
124 
125     @Override
126     public int[] getDefaultTokens() {
127         return getRequiredTokens();
128     }
129 
130     @Override
131     public int[] getAcceptableTokens() {
132         return getRequiredTokens();
133     }
134 
135     @Override
136     public int[] getRequiredTokens() {
137         return new int[] {
138             TokenTypes.LITERAL_CATCH,
139         };
140     }
141 
142     @Override
143     public boolean isCommentNodesRequired() {
144         return true;
145     }
146 
147     @Override
148     public void visitToken(DetailAST ast) {
149         visitCatchBlock(ast);
150     }
151 
152     /**
153      * Visits catch ast node, if it is empty catch block - checks it according to
154      *  Check's options. If exception's variable name or comment inside block are matching
155      *   specified regexp - skips from consideration, else - puts violation.
156      *
157      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
158      */
159     private void visitCatchBlock(DetailAST catchAst) {
160         if (isEmptyCatchBlock(catchAst)) {
161             final String commentContent = getCommentFirstLine(catchAst);
162             if (isVerifiable(catchAst, commentContent)) {
163                 log(catchAst.findFirstToken(TokenTypes.SLIST), MSG_KEY_CATCH_BLOCK_EMPTY);
164             }
165         }
166     }
167 
168     /**
169      * Gets the first line of comment in catch block. If comment is single-line -
170      *  returns it fully, else if comment is multi-line - returns the first line.
171      *
172      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
173      * @return the first line of comment in catch block, "" if no comment was found.
174      */
175     private static String getCommentFirstLine(DetailAST catchAst) {
176         final DetailAST slistToken = catchAst.getLastChild();
177         final DetailAST firstElementInBlock = slistToken.getFirstChild();
178         String commentContent = "";
179         if (firstElementInBlock.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
180             commentContent = firstElementInBlock.getFirstChild().getText();
181         }
182         else if (firstElementInBlock.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
183             commentContent = firstElementInBlock.getFirstChild().getText();
184             final String[] lines = LINE_END_PATTERN.split(commentContent);
185             for (String line : lines) {
186                 if (!line.isEmpty()) {
187                     commentContent = line;
188                     break;
189                 }
190             }
191         }
192         return commentContent;
193     }
194 
195     /**
196      * Checks if current empty catch block is verifiable according to Check's options
197      *  (exception's variable name and comment format are both in consideration).
198      *
199      * @param emptyCatchAst empty catch {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH} block.
200      * @param commentContent text of comment.
201      * @return true if empty catch block is verifiable by Check.
202      */
203     private boolean isVerifiable(DetailAST emptyCatchAst, String commentContent) {
204         final String variableName = getExceptionVariableName(emptyCatchAst);
205         final boolean isMatchingVariableName = exceptionVariableName
206                 .matcher(variableName).find();
207         final boolean isMatchingCommentContent = !commentContent.isEmpty()
208                  && commentFormat.matcher(commentContent).find();
209         return !isMatchingVariableName && !isMatchingCommentContent;
210     }
211 
212     /**
213      * Checks if catch block is empty or contains only comments.
214      *
215      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
216      * @return true if catch block is empty.
217      */
218     private static boolean isEmptyCatchBlock(DetailAST catchAst) {
219         boolean result = true;
220         final DetailAST slistToken = catchAst.findFirstToken(TokenTypes.SLIST);
221         DetailAST catchBlockStmt = slistToken.getFirstChild();
222         while (catchBlockStmt.getType() != TokenTypes.RCURLY) {
223             if (catchBlockStmt.getType() != TokenTypes.SINGLE_LINE_COMMENT
224                  && catchBlockStmt.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) {
225                 result = false;
226                 break;
227             }
228             catchBlockStmt = catchBlockStmt.getNextSibling();
229         }
230         return result;
231     }
232 
233     /**
234      * Gets variable's name associated with exception.
235      *
236      * @param catchAst {@link TokenTypes#LITERAL_CATCH LITERAL_CATCH}
237      * @return Variable's name associated with exception.
238      */
239     private static String getExceptionVariableName(DetailAST catchAst) {
240         final DetailAST parameterDef = catchAst.findFirstToken(TokenTypes.PARAMETER_DEF);
241         final DetailAST variableName = parameterDef.findFirstToken(TokenTypes.IDENT);
242         return variableName.getText();
243     }
244 
245 }