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 }