1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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 @Override
89 public int[] getDefaultTokens() {
90 return getRequiredTokens();
91 }
92
93 @Override
94 public int[] getAcceptableTokens() {
95 return getRequiredTokens();
96 }
97
98 @Override
99 public int[] getRequiredTokens() {
100 return new int[] {
101 TokenTypes.LAMBDA,
102 TokenTypes.IDENT,
103 };
104 }
105
106 @Override
107 public void beginTree(DetailAST rootAST) {
108 lambdaParameters.clear();
109 }
110
111 @Override
112 public void visitToken(DetailAST ast) {
113 if (ast.getType() == TokenTypes.LAMBDA) {
114 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
115 if (parameters != null) {
116 // we have multiple lambda parameters
117 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, parameter -> {
118 final DetailAST identifierAst = parameter.findFirstToken(TokenTypes.IDENT);
119 final LambdaParameterDetails lambdaParameter =
120 new LambdaParameterDetails(ast, identifierAst);
121 lambdaParameters.push(lambdaParameter);
122 });
123 }
124 else if (ast.getChildCount() != 0) {
125 // we are not switch rule and have a single parameter
126 final LambdaParameterDetails lambdaParameter =
127 new LambdaParameterDetails(ast, ast.findFirstToken(TokenTypes.IDENT));
128 lambdaParameters.push(lambdaParameter);
129 }
130 }
131 else if (isLambdaParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) {
132 // we do not count reassignment as usage
133 lambdaParameters.stream()
134 .filter(parameter -> parameter.getName().equals(ast.getText()))
135 .findFirst()
136 .ifPresent(LambdaParameterDetails::registerAsUsed);
137 }
138 }
139
140 @Override
141 public void leaveToken(DetailAST ast) {
142 while (lambdaParameters.peek() != null
143 && ast.equals(lambdaParameters.peek().enclosingLambda)) {
144
145 final Optional<LambdaParameterDetails> unusedLambdaParameter =
146 Optional.ofNullable(lambdaParameters.peek())
147 .filter(parameter -> !parameter.isUsed())
148 .filter(parameter -> !"_".equals(parameter.getName()));
149
150 unusedLambdaParameter.ifPresent(parameter -> {
151 log(parameter.getIdentifierAst(),
152 MSG_UNUSED_LAMBDA_PARAMETER,
153 parameter.getName());
154 });
155 lambdaParameters.pop();
156 }
157 }
158
159 /**
160 * Visit ast of type {@link TokenTypes#IDENT}
161 * and check if it is a candidate for a lambda parameter identifier.
162 *
163 * @param identifierAst token representing {@link TokenTypes#IDENT}
164 * @return true if the given {@link TokenTypes#IDENT} could be a lambda parameter identifier
165 */
166 private static boolean isLambdaParameterIdentifierCandidate(DetailAST identifierAst) {
167 // we should ignore the ident if it is in the lambda parameters declaration
168 final boolean isLambdaParameterDeclaration =
169 identifierAst.getParent().getType() == TokenTypes.LAMBDA
170 || identifierAst.getParent().getType() == TokenTypes.PARAMETER_DEF;
171
172 return !isLambdaParameterDeclaration
173 && (hasValidParentToken(identifierAst) || isMethodInvocation(identifierAst));
174 }
175
176 /**
177 * Check if the given {@link TokenTypes#IDENT} has a valid parent token.
178 * A valid parent token is a token that can be a parent for a lambda parameter identifier.
179 *
180 * @param identifierAst token representing {@link TokenTypes#IDENT}
181 * @return true if the given {@link TokenTypes#IDENT} has a valid parent token
182 */
183 private static boolean hasValidParentToken(DetailAST identifierAst) {
184 return !TokenUtil.isOfType(identifierAst.getParent(), INVALID_LAMBDA_PARAM_IDENT_PARENTS);
185 }
186
187 /**
188 * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator
189 * and is a candidate for lambda parameter.
190 *
191 * @param identAst token representing {@link TokenTypes#IDENT}
192 * @return true if the given {@link TokenTypes#IDENT} is a child of a dot operator
193 * and a candidate for lambda parameter.
194 */
195 private static boolean isMethodInvocation(DetailAST identAst) {
196 final DetailAST parent = identAst.getParent();
197 return parent.getType() == TokenTypes.DOT
198 && identAst.equals(parent.getFirstChild());
199 }
200
201 /**
202 * Check if the given {@link TokenTypes#IDENT} is a left hand side value.
203 *
204 * @param identAst token representing {@link TokenTypes#IDENT}
205 * @return true if the given {@link TokenTypes#IDENT} is a left hand side value.
206 */
207 private static boolean isLeftHandOfAssignment(DetailAST identAst) {
208 final DetailAST parent = identAst.getParent();
209 return parent.getType() == TokenTypes.ASSIGN
210 && !identAst.equals(parent.getLastChild());
211 }
212
213 /**
214 * Maintains information about the lambda parameter.
215 */
216 private static final class LambdaParameterDetails {
217
218 /**
219 * Ast of type {@link TokenTypes#LAMBDA} enclosing the lambda
220 * parameter.
221 */
222 private final DetailAST enclosingLambda;
223
224 /**
225 * Ast of type {@link TokenTypes#IDENT} of the given
226 * lambda parameter.
227 */
228 private final DetailAST identifierAst;
229
230 /**
231 * Is the variable used.
232 */
233 private boolean used;
234
235 /**
236 * Create a new lambda parameter instance.
237 *
238 * @param enclosingLambda ast of type {@link TokenTypes#LAMBDA}
239 * @param identifierAst ast of type {@link TokenTypes#IDENT}
240 */
241 private LambdaParameterDetails(DetailAST enclosingLambda, DetailAST identifierAst) {
242 this.enclosingLambda = enclosingLambda;
243 this.identifierAst = identifierAst;
244 }
245
246 /**
247 * Register the lambda parameter as used.
248 */
249 private void registerAsUsed() {
250 used = true;
251 }
252
253 /**
254 * Get the name of the lambda parameter.
255 *
256 * @return the name of the lambda parameter
257 */
258 private String getName() {
259 return identifierAst.getText();
260 }
261
262 /**
263 * Get ast of type {@link TokenTypes#IDENT} of the given
264 * lambda parameter.
265 *
266 * @return ast of type {@link TokenTypes#IDENT} of the given lambda parameter
267 */
268 private DetailAST getIdentifierAst() {
269 return identifierAst;
270 }
271
272 /**
273 * Check if the lambda parameter is used.
274 *
275 * @return true if the lambda parameter is used
276 */
277 private boolean isUsed() {
278 return used;
279 }
280 }
281 }