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   *
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 }