001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.Optional;
025
026import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
031
032/**
033 * <div>
034 * Ensures that lambda parameters that are not used are declared as an unnamed variable.
035 * </div>
036 *
037 * <p>
038 * Rationale:
039 * </p>
040 * <ul>
041 *     <li>
042 *         Improves code readability by clearly indicating which parameters are unused.
043 *     </li>
044 *     <li>
045 *         Follows Java conventions for denoting unused parameters with an underscore ({@code _}).
046 *     </li>
047 * </ul>
048 *
049 * <p>
050 * See the <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
051 * Java Language Specification</a> for more information about unnamed variables.
052 * </p>
053 *
054 * <p>
055 * <b>Attention</b>: Unnamed variables are available as a preview feature in Java 21,
056 * and became an official part of the language in Java 22.
057 * This check should be activated only on source code which meets those requirements.
058 * </p>
059 *
060 * @since 10.18.0
061 */
062@FileStatefulCheck
063public class UnusedLambdaParameterShouldBeUnnamedCheck extends AbstractCheck {
064
065    /**
066     * A key is pointing to the warning message text in "messages.properties"
067     * file.
068     */
069    public static final String MSG_UNUSED_LAMBDA_PARAMETER = "unused.lambda.parameter";
070
071    /**
072     * Invalid parents of the lambda parameter identifier.
073     * These are tokens that can not be parents for a lambda
074     * parameter identifier.
075     */
076    private static final int[] INVALID_LAMBDA_PARAM_IDENT_PARENTS = {
077        TokenTypes.DOT,
078        TokenTypes.LITERAL_NEW,
079        TokenTypes.METHOD_CALL,
080        TokenTypes.TYPE,
081    };
082
083    /**
084     * Keeps track of the lambda parameters in a block.
085     */
086    private final Deque<LambdaParameterDetails> lambdaParameters = new ArrayDeque<>();
087
088    /**
089     * Creates a new {@code UnusedLambdaParameterShouldBeUnnamedCheck} instance.
090     */
091    public UnusedLambdaParameterShouldBeUnnamedCheck() {
092        // no code by default
093    }
094
095    @Override
096    public int[] getDefaultTokens() {
097        return getRequiredTokens();
098    }
099
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 {@link TokenTypes#IDENT}
171     * @return true if the given {@link 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 {@link TokenTypes#IDENT}
188     * @return true if the given {@link 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 {@link TokenTypes#IDENT}
199     * @return true if the given {@link 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 {@link TokenTypes#IDENT}
212     * @return true if the given {@link 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 {@link 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}