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 lambda 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>: Unnamed variables are available as a preview feature in Java 21,
56 * and became an official part of the language in Java 22.
57 * This check should be activated only on source code which meets those requirements.
58 * </p>
59 *
60 * @since 10.18.0
61 */
62 @FileStatefulCheck
63 public class UnusedLambdaParameterShouldBeUnnamedCheck extends AbstractCheck {
64
65 /**
66 * A key is pointing to the warning message text in "messages.properties"
67 * file.
68 */
69 public static final String MSG_UNUSED_LAMBDA_PARAMETER = "unused.lambda.parameter";
70
71 /**
72 * Invalid parents of the lambda parameter identifier.
73 * These are tokens that can not be parents for a lambda
74 * parameter identifier.
75 */
76 private static final int[] INVALID_LAMBDA_PARAM_IDENT_PARENTS = {
77 TokenTypes.DOT,
78 TokenTypes.LITERAL_NEW,
79 TokenTypes.METHOD_CALL,
80 TokenTypes.TYPE,
81 };
82
83 /**
84 * Keeps track of the lambda parameters in a block.
85 */
86 private final Deque<LambdaParameterDetails> lambdaParameters = new ArrayDeque<>();
87
88 /**
89 * Creates a new {@code UnusedLambdaParameterShouldBeUnnamedCheck} instance.
90 */
91 public UnusedLambdaParameterShouldBeUnnamedCheck() {
92 // no code by default
93 }
94
95 @Override
96 public int[] getDefaultTokens() {
97 return getRequiredTokens();
98 }
99
100 @Override
101 public int[] getAcceptableTokens() {
102 return getRequiredTokens();
103 }
104
105 @Override
106 public int[] getRequiredTokens() {
107 return new int[] {
108 TokenTypes.LAMBDA,
109 TokenTypes.IDENT,
110 };
111 }
112
113 @Override
114 public void beginTree(DetailAST rootAST) {
115 lambdaParameters.clear();
116 }
117
118 @Override
119 public void visitToken(DetailAST ast) {
120 if (ast.getType() == TokenTypes.LAMBDA) {
121 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
122 if (parameters != null) {
123 // we have multiple lambda parameters
124 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, parameter -> {
125 final DetailAST identifierAst = parameter.findFirstToken(TokenTypes.IDENT);
126 final LambdaParameterDetails lambdaParameter =
127 new LambdaParameterDetails(ast, identifierAst);
128 lambdaParameters.push(lambdaParameter);
129 });
130 }
131 else if (ast.getChildCount() != 0) {
132 // we are not switch rule and have a single parameter
133 final LambdaParameterDetails lambdaParameter =
134 new LambdaParameterDetails(ast, ast.findFirstToken(TokenTypes.IDENT));
135 lambdaParameters.push(lambdaParameter);
136 }
137 }
138 else if (isLambdaParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) {
139 // we do not count reassignment as usage
140 lambdaParameters.stream()
141 .filter(parameter -> parameter.getName().equals(ast.getText()))
142 .findFirst()
143 .ifPresent(LambdaParameterDetails::registerAsUsed);
144 }
145 }
146
147 @Override
148 public void leaveToken(DetailAST ast) {
149 while (lambdaParameters.peek() != null
150 && ast.equals(lambdaParameters.peek().enclosingLambda)) {
151
152 final Optional<LambdaParameterDetails> unusedLambdaParameter =
153 Optional.ofNullable(lambdaParameters.peek())
154 .filter(parameter -> !parameter.isUsed())
155 .filter(parameter -> !"_".equals(parameter.getName()));
156
157 unusedLambdaParameter.ifPresent(parameter -> {
158 log(parameter.getIdentifierAst(),
159 MSG_UNUSED_LAMBDA_PARAMETER,
160 parameter.getName());
161 });
162 lambdaParameters.pop();
163 }
164 }
165
166 /**
167 * Visit ast of type {@link TokenTypes#IDENT}
168 * and check if it is a candidate for a lambda parameter identifier.
169 *
170 * @param identifierAst token representing {@code TokenTypes#IDENT}
171 * @return true if the given {@code TokenTypes#IDENT} could be a lambda parameter identifier
172 */
173 private static boolean isLambdaParameterIdentifierCandidate(DetailAST identifierAst) {
174 // we should ignore the ident if it is in the lambda parameters declaration
175 final boolean isLambdaParameterDeclaration =
176 identifierAst.getParent().getType() == TokenTypes.LAMBDA
177 || identifierAst.getParent().getType() == TokenTypes.PARAMETER_DEF;
178
179 return !isLambdaParameterDeclaration
180 && (hasValidParentToken(identifierAst) || isMethodInvocation(identifierAst));
181 }
182
183 /**
184 * Check if the given {@link TokenTypes#IDENT} has a valid parent token.
185 * A valid parent token is a token that can be a parent for a lambda parameter identifier.
186 *
187 * @param identifierAst token representing {@code TokenTypes#IDENT}
188 * @return true if the given {@code TokenTypes#IDENT} has a valid parent token
189 */
190 private static boolean hasValidParentToken(DetailAST identifierAst) {
191 return !TokenUtil.isOfType(identifierAst.getParent(), INVALID_LAMBDA_PARAM_IDENT_PARENTS);
192 }
193
194 /**
195 * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator
196 * and is a candidate for lambda parameter.
197 *
198 * @param identAst token representing {@code TokenTypes#IDENT}
199 * @return true if the given {@code TokenTypes#IDENT} is a child of a dot operator
200 * and a candidate for lambda parameter.
201 */
202 private static boolean isMethodInvocation(DetailAST identAst) {
203 final DetailAST parent = identAst.getParent();
204 return parent.getType() == TokenTypes.DOT
205 && identAst.equals(parent.getFirstChild());
206 }
207
208 /**
209 * Check if the given {@link TokenTypes#IDENT} is a left hand side value.
210 *
211 * @param identAst token representing {@code TokenTypes#IDENT}
212 * @return true if the given {@code TokenTypes#IDENT} is a left hand side value.
213 */
214 private static boolean isLeftHandOfAssignment(DetailAST identAst) {
215 final DetailAST parent = identAst.getParent();
216 return parent.getType() == TokenTypes.ASSIGN
217 && !identAst.equals(parent.getLastChild());
218 }
219
220 /**
221 * Maintains information about the lambda parameter.
222 */
223 private static final class LambdaParameterDetails {
224
225 /**
226 * Ast of type {@link TokenTypes#LAMBDA} enclosing the lambda
227 * parameter.
228 */
229 private final DetailAST enclosingLambda;
230
231 /**
232 * Ast of type {@link TokenTypes#IDENT} of the given
233 * lambda parameter.
234 */
235 private final DetailAST identifierAst;
236
237 /**
238 * Is the variable used.
239 */
240 private boolean used;
241
242 /**
243 * Create a new lambda parameter instance.
244 *
245 * @param enclosingLambda ast of type {@link TokenTypes#LAMBDA}
246 * @param identifierAst ast of type {@link TokenTypes#IDENT}
247 */
248 private LambdaParameterDetails(DetailAST enclosingLambda, DetailAST identifierAst) {
249 this.enclosingLambda = enclosingLambda;
250 this.identifierAst = identifierAst;
251 }
252
253 /**
254 * Register the lambda parameter as used.
255 */
256 private void registerAsUsed() {
257 used = true;
258 }
259
260 /**
261 * Get the name of the lambda parameter.
262 *
263 * @return the name of the lambda parameter
264 */
265 private String getName() {
266 return identifierAst.getText();
267 }
268
269 /**
270 * Get ast of type {@link TokenTypes#IDENT} of the given
271 * lambda parameter.
272 *
273 * @return ast of type {@code TokenTypes#IDENT} of the given lambda parameter
274 */
275 private DetailAST getIdentifierAst() {
276 return identifierAst;
277 }
278
279 /**
280 * Check if the lambda parameter is used.
281 *
282 * @return true if the lambda parameter is used
283 */
284 private boolean isUsed() {
285 return used;
286 }
287 }
288
289 }