View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <p>
61   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
62   * </p>
63   *
64   * <p>
65   * Violation Message Keys:
66   * </p>
67   * <ul>
68   * <li>
69   * {@code unused.catch.parameter}
70   * </li>
71   * </ul>
72   *
73   * @since 10.18.0
74   *
75   */
76  
77  @FileStatefulCheck
78  public class UnusedCatchParameterShouldBeUnnamedCheck extends AbstractCheck {
79  
80      /**
81       * A key is pointing to the warning message text in "messages.properties"
82       * file.
83       */
84      public static final String MSG_UNUSED_CATCH_PARAMETER = "unused.catch.parameter";
85  
86      /**
87       * Invalid parents of the catch parameter identifier.
88       */
89      private static final int[] INVALID_CATCH_PARAM_IDENT_PARENTS = {
90          TokenTypes.DOT,
91          TokenTypes.LITERAL_NEW,
92          TokenTypes.METHOD_CALL,
93          TokenTypes.TYPE,
94      };
95  
96      /**
97       * Keeps track of the catch parameters in a block.
98       */
99      private final Deque<CatchParameterDetails> catchParameters = new ArrayDeque<>();
100 
101     @Override
102     public int[] getDefaultTokens() {
103         return getRequiredTokens();
104     }
105 
106     @Override
107     public int[] getAcceptableTokens() {
108         return getRequiredTokens();
109     }
110 
111     @Override
112     public int[] getRequiredTokens() {
113         return new int[] {
114             TokenTypes.LITERAL_CATCH,
115             TokenTypes.IDENT,
116         };
117     }
118 
119     @Override
120     public void beginTree(DetailAST rootAST) {
121         catchParameters.clear();
122     }
123 
124     @Override
125     public void visitToken(DetailAST ast) {
126         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
127             final CatchParameterDetails catchParameter = new CatchParameterDetails(ast);
128             catchParameters.push(catchParameter);
129         }
130         else if (isCatchParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) {
131             // we do not count reassignment as usage
132             catchParameters.stream()
133                     .filter(parameter -> parameter.getName().equals(ast.getText()))
134                     .findFirst()
135                     .ifPresent(CatchParameterDetails::registerAsUsed);
136         }
137     }
138 
139     @Override
140     public void leaveToken(DetailAST ast) {
141         if (ast.getType() == TokenTypes.LITERAL_CATCH) {
142             final Optional<CatchParameterDetails> unusedCatchParameter =
143                     Optional.ofNullable(catchParameters.peek())
144                             .filter(parameter -> !parameter.isUsed())
145                             .filter(parameter -> !"_".equals(parameter.getName()));
146 
147             unusedCatchParameter.ifPresent(parameter -> {
148                 log(parameter.getParameterDefinition(),
149                         MSG_UNUSED_CATCH_PARAMETER,
150                         parameter.getName());
151             });
152             catchParameters.pop();
153         }
154     }
155 
156     /**
157      * Visit ast of type {@link TokenTypes#IDENT}
158      * and check if it is a candidate for a catch parameter identifier.
159      *
160      * @param identifierAst token representing {@link TokenTypes#IDENT}
161      * @return true if the given {@link TokenTypes#IDENT} could be a catch parameter identifier
162      */
163     private static boolean isCatchParameterIdentifierCandidate(DetailAST identifierAst) {
164         // we should ignore the ident if it is in the exception declaration
165         return identifierAst.getParent().getParent().getType() != TokenTypes.LITERAL_CATCH
166             && (!TokenUtil.isOfType(identifierAst.getParent(), INVALID_CATCH_PARAM_IDENT_PARENTS)
167                  || isMethodInvocation(identifierAst));
168     }
169 
170     /**
171      * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator
172      * and is a candidate for catch parameter.
173      *
174      * @param identAst token representing {@link TokenTypes#IDENT}
175      * @return true if the given {@link TokenTypes#IDENT} is a child of a dot operator
176      *     and a candidate for catch parameter.
177      */
178     private static boolean isMethodInvocation(DetailAST identAst) {
179         final DetailAST parent = identAst.getParent();
180         return parent.getType() == TokenTypes.DOT
181                 && identAst.equals(parent.getFirstChild());
182     }
183 
184     /**
185      * Check if the given {@link TokenTypes#IDENT} is a left hand side value.
186      *
187      * @param identAst token representing {@link TokenTypes#IDENT}
188      * @return true if the given {@link TokenTypes#IDENT} is a left hand side value.
189      */
190     private static boolean isLeftHandOfAssignment(DetailAST identAst) {
191         final DetailAST parent = identAst.getParent();
192         return parent.getType() == TokenTypes.ASSIGN
193                 && !identAst.equals(parent.getLastChild());
194     }
195 
196     /**
197      * Maintains information about the catch parameter.
198      */
199     private static final class CatchParameterDetails {
200 
201         /**
202          * The name of the catch parameter.
203          */
204         private final String name;
205 
206         /**
207          * Ast of type {@link TokenTypes#PARAMETER_DEF} to use it when logging.
208          */
209         private final DetailAST parameterDefinition;
210 
211         /**
212          * Is the variable used.
213          */
214         private boolean used;
215 
216         /**
217          * Create a new catch parameter instance.
218          *
219          * @param enclosingCatchClause ast of type {@link TokenTypes#LITERAL_CATCH}
220          */
221         private CatchParameterDetails(DetailAST enclosingCatchClause) {
222             parameterDefinition =
223                     enclosingCatchClause.findFirstToken(TokenTypes.PARAMETER_DEF);
224             name = parameterDefinition.findFirstToken(TokenTypes.IDENT).getText();
225         }
226 
227         /**
228          * Register the catch parameter as used.
229          */
230         private void registerAsUsed() {
231             used = true;
232         }
233 
234         /**
235          * Get the name of the catch parameter.
236          *
237          * @return the name of the catch parameter
238          */
239         private String getName() {
240             return name;
241         }
242 
243         /**
244          * Check if the catch parameter is used.
245          *
246          * @return true if the catch parameter is used
247          */
248         private boolean isUsed() {
249             return used;
250         }
251 
252         /**
253          * Get the parameter definition token of the catch parameter
254          * represented by ast of type {@link TokenTypes#PARAMETER_DEF}.
255          *
256          * @return the ast of type {@link TokenTypes#PARAMETER_DEF}
257          */
258         private DetailAST getParameterDefinition() {
259             return parameterDefinition;
260         }
261     }
262 }