View Javadoc
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.coding;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.Optional;
25  
26  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
31  
32  /**
33   * <div>
34   * Ensures that catch parameters that are not used are declared as an unnamed variable.
35   * </div>
36   *
37   * <p>
38   * Rationale:
39   * </p>
40   * <ul>
41   *     <li>
42   *         Improves code readability by clearly indicating which parameters are unused.
43   *     </li>
44   *     <li>
45   *         Follows Java conventions for denoting unused parameters with an underscore ({@code _}).
46   *     </li>
47   * </ul>
48   *
49   * <p>
50   * See the <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
51   * Java Language Specification</a> for more information about unnamed variables.
52   * </p>
53   *
54   * <p>
55   * <b>Attention</b>: This check should be activated only on source code
56   * that is compiled by jdk21 or higher;
57   * unnamed catch parameters came out as the first preview in Java 21.
58   * </p>
59   *
60   * @since 10.18.0
61   */
62  
63  @FileStatefulCheck
64  public class UnusedCatchParameterShouldBeUnnamedCheck extends AbstractCheck {
65  
66      /**
67       * A key is pointing to the warning message text in "messages.properties"
68       * file.
69       */
70      public static final String MSG_UNUSED_CATCH_PARAMETER = "unused.catch.parameter";
71  
72      /**
73       * Invalid parents of the catch parameter identifier.
74       */
75      private static final int[] INVALID_CATCH_PARAM_IDENT_PARENTS = {
76          TokenTypes.DOT,
77          TokenTypes.LITERAL_NEW,
78          TokenTypes.METHOD_CALL,
79          TokenTypes.TYPE,
80      };
81  
82      /**
83       * Keeps track of the catch parameters in a block.
84       */
85      private final Deque<CatchParameterDetails> catchParameters = new ArrayDeque<>();
86  
87      /**
88       * Creates a new {@code UnusedCatchParameterShouldBeUnnamedCheck} instance.
89       */
90      public UnusedCatchParameterShouldBeUnnamedCheck() {
91          // no code by default
92      }
93  
94      @Override
95      public int[] getDefaultTokens() {
96          return getRequiredTokens();
97      }
98  
99      @Override
100     public int[] getAcceptableTokens() {
101         return getRequiredTokens();
102     }
103 
104     @Override
105     public int[] getRequiredTokens() {
106         return new int[] {
107             TokenTypes.LITERAL_CATCH,
108             TokenTypes.IDENT,
109         };
110     }
111 
112     @Override
113     public void beginTree(DetailAST rootAST) {
114         catchParameters.clear();
115     }
116 
117     @Override
118     public void visitToken(DetailAST ast) {
119         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
120             final CatchParameterDetails catchParameter = new CatchParameterDetails(ast);
121             catchParameters.push(catchParameter);
122         }
123         else if (isCatchParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) {
124             // we do not count reassignment as usage
125             catchParameters.stream()
126                     .filter(parameter -> parameter.getName().equals(ast.getText()))
127                     .findFirst()
128                     .ifPresent(CatchParameterDetails::registerAsUsed);
129         }
130     }
131 
132     @Override
133     public void leaveToken(DetailAST ast) {
134         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
135             final Optional<CatchParameterDetails> unusedCatchParameter =
136                     Optional.ofNullable(catchParameters.peek())
137                             .filter(parameter -> !parameter.isUsed())
138                             .filter(parameter -> !"_".equals(parameter.getName()));
139 
140             unusedCatchParameter.ifPresent(parameter -> {
141                 log(parameter.getParameterDefinition(),
142                         MSG_UNUSED_CATCH_PARAMETER,
143                         parameter.getName());
144             });
145             catchParameters.pop();
146         }
147     }
148 
149     /**
150      * Visit ast of type {@link TokenTypes#IDENT}
151      * and check if it is a candidate for a catch parameter identifier.
152      *
153      * @param identifierAst token representing {@code TokenTypes#IDENT}
154      * @return true if the given {@code TokenTypes#IDENT} could be a catch parameter identifier
155      */
156     private static boolean isCatchParameterIdentifierCandidate(DetailAST identifierAst) {
157         // we should ignore the ident if it is in the exception declaration
158         return identifierAst.getParent().getParent().getType() != TokenTypes.LITERAL_CATCH
159             && (!TokenUtil.isOfType(identifierAst.getParent(), INVALID_CATCH_PARAM_IDENT_PARENTS)
160                  || isMethodInvocation(identifierAst));
161     }
162 
163     /**
164      * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator
165      * and is a candidate for catch parameter.
166      *
167      * @param identAst token representing {@code TokenTypes#IDENT}
168      * @return true if the given {@code TokenTypes#IDENT} is a child of a dot operator
169      *     and a candidate for catch parameter.
170      */
171     private static boolean isMethodInvocation(DetailAST identAst) {
172         final DetailAST parent = identAst.getParent();
173         return parent.getType() == TokenTypes.DOT
174                 && identAst.equals(parent.getFirstChild());
175     }
176 
177     /**
178      * Check if the given {@link TokenTypes#IDENT} is a left hand side value.
179      *
180      * @param identAst token representing {@code TokenTypes#IDENT}
181      * @return true if the given {@code TokenTypes#IDENT} is a left hand side value.
182      */
183     private static boolean isLeftHandOfAssignment(DetailAST identAst) {
184         final DetailAST parent = identAst.getParent();
185         return parent.getType() == TokenTypes.ASSIGN
186                 && !identAst.equals(parent.getLastChild());
187     }
188 
189     /**
190      * Maintains information about the catch parameter.
191      */
192     private static final class CatchParameterDetails {
193 
194         /**
195          * The name of the catch parameter.
196          */
197         private final String name;
198 
199         /**
200          * Ast of type {@link TokenTypes#PARAMETER_DEF} to use it when logging.
201          */
202         private final DetailAST parameterDefinition;
203 
204         /**
205          * Is the variable used.
206          */
207         private boolean used;
208 
209         /**
210          * Create a new catch parameter instance.
211          *
212          * @param enclosingCatchClause ast of type {@link TokenTypes#LITERAL_CATCH}
213          */
214         private CatchParameterDetails(DetailAST enclosingCatchClause) {
215             parameterDefinition =
216                     enclosingCatchClause.findFirstToken(TokenTypes.PARAMETER_DEF);
217             name = parameterDefinition.findFirstToken(TokenTypes.IDENT).getText();
218         }
219 
220         /**
221          * Register the catch parameter as used.
222          */
223         private void registerAsUsed() {
224             used = true;
225         }
226 
227         /**
228          * Get the name of the catch parameter.
229          *
230          * @return the name of the catch parameter
231          */
232         private String getName() {
233             return name;
234         }
235 
236         /**
237          * Check if the catch parameter is used.
238          *
239          * @return true if the catch parameter is used
240          */
241         private boolean isUsed() {
242             return used;
243         }
244 
245         /**
246          * Get the parameter definition token of the catch parameter
247          * represented by ast of type {@link TokenTypes#PARAMETER_DEF}.
248          *
249          * @return the ast of type {@code TokenTypes#PARAMETER_DEF}
250          */
251         private DetailAST getParameterDefinition() {
252             return parameterDefinition;
253         }
254     }
255 
256 }