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