View Javadoc
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 }