001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 * <p>
034 * Ensures that lambda parameters that are not used are declared as an unnamed variable.
035 * </p>
036 * <p>
037 * Rationale:
038 * </p>
039 * <ul>
040 *     <li>
041 *         Improves code readability by clearly indicating which parameters are unused.
042 *     </li>
043 *     <li>
044 *         Follows Java conventions for denoting unused parameters with an underscore ({@code _}).
045 *     </li>
046 * </ul>
047 * <p>
048 * See the <a href="https://docs.oracle.com/en/java/javase/21/docs/specs/unnamed-jls.html">
049 * Java Language Specification</a> for more information about unnamed variables.
050 * </p>
051 * <p>
052 * <b>Attention</b>: Unnamed variables are available as a preview feature in Java 21,
053 * and became an official part of the language in Java 22.
054 * This check should be activated only on source code which meets those requirements.
055 * </p>
056 * <p>
057 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
058 * </p>
059 * <p>
060 * Violation Message Keys:
061 * </p>
062 * <ul>
063 * <li>
064 * {@code unused.lambda.parameter}
065 * </li>
066 * </ul>
067 *
068 * @since 10.18.0
069 */
070@FileStatefulCheck
071public class UnusedLambdaParameterShouldBeUnnamedCheck extends AbstractCheck {
072
073    /**
074     * A key is pointing to the warning message text in "messages.properties"
075     * file.
076     */
077    public static final String MSG_UNUSED_LAMBDA_PARAMETER = "unused.lambda.parameter";
078
079    /**
080     * Invalid parents of the lambda parameter identifier.
081     * These are tokens that can not be parents for a lambda
082     * parameter identifier.
083     */
084    private static final int[] INVALID_LAMBDA_PARAM_IDENT_PARENTS = {
085        TokenTypes.DOT,
086        TokenTypes.LITERAL_NEW,
087        TokenTypes.METHOD_CALL,
088        TokenTypes.TYPE,
089    };
090
091    /**
092     * Keeps track of the lambda parameters in a block.
093     */
094    private final Deque<LambdaParameterDetails> lambdaParameters = new ArrayDeque<>();
095
096    @Override
097    public int[] getDefaultTokens() {
098        return getRequiredTokens();
099    }
100
101    @Override
102    public int[] getAcceptableTokens() {
103        return getRequiredTokens();
104    }
105
106    @Override
107    public int[] getRequiredTokens() {
108        return new int[] {
109            TokenTypes.LAMBDA,
110            TokenTypes.IDENT,
111        };
112    }
113
114    @Override
115    public void beginTree(DetailAST rootAST) {
116        lambdaParameters.clear();
117    }
118
119    @Override
120    public void visitToken(DetailAST ast) {
121        if (ast.getType() == TokenTypes.LAMBDA) {
122            final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
123            if (parameters != null) {
124                // we have multiple lambda parameters
125                TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, parameter -> {
126                    final DetailAST identifierAst = parameter.findFirstToken(TokenTypes.IDENT);
127                    final LambdaParameterDetails lambdaParameter =
128                            new LambdaParameterDetails(ast, identifierAst);
129                    lambdaParameters.push(lambdaParameter);
130                });
131            }
132            else if (ast.getChildCount() != 0) {
133                // we are not switch rule and have a single parameter
134                final LambdaParameterDetails lambdaParameter =
135                            new LambdaParameterDetails(ast, ast.findFirstToken(TokenTypes.IDENT));
136                lambdaParameters.push(lambdaParameter);
137            }
138        }
139        else if (isLambdaParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) {
140            // we do not count reassignment as usage
141            lambdaParameters.stream()
142                    .filter(parameter -> parameter.getName().equals(ast.getText()))
143                    .findFirst()
144                    .ifPresent(LambdaParameterDetails::registerAsUsed);
145        }
146    }
147
148    @Override
149    public void leaveToken(DetailAST ast) {
150        while (lambdaParameters.peek() != null
151                    && ast.equals(lambdaParameters.peek().enclosingLambda)) {
152
153            final Optional<LambdaParameterDetails> unusedLambdaParameter =
154                    Optional.ofNullable(lambdaParameters.peek())
155                            .filter(parameter -> !parameter.isUsed())
156                            .filter(parameter -> !"_".equals(parameter.getName()));
157
158            unusedLambdaParameter.ifPresent(parameter -> {
159                log(parameter.getIdentifierAst(),
160                        MSG_UNUSED_LAMBDA_PARAMETER,
161                        parameter.getName());
162            });
163            lambdaParameters.pop();
164        }
165    }
166
167    /**
168     * Visit ast of type {@link TokenTypes#IDENT}
169     * and check if it is a candidate for a lambda parameter identifier.
170     *
171     * @param identifierAst token representing {@link TokenTypes#IDENT}
172     * @return true if the given {@link TokenTypes#IDENT} could be a lambda parameter identifier
173     */
174    private static boolean isLambdaParameterIdentifierCandidate(DetailAST identifierAst) {
175        // we should ignore the ident if it is in the lambda parameters declaration
176        final boolean isLambdaParameterDeclaration =
177                identifierAst.getParent().getType() == TokenTypes.LAMBDA
178                    || identifierAst.getParent().getType() == TokenTypes.PARAMETER_DEF;
179
180        return !isLambdaParameterDeclaration
181                 && (hasValidParentToken(identifierAst) || isMethodInvocation(identifierAst));
182    }
183
184    /**
185     * Check if the given {@link TokenTypes#IDENT} has a valid parent token.
186     * A valid parent token is a token that can be a parent for a lambda parameter identifier.
187     *
188     * @param identifierAst token representing {@link TokenTypes#IDENT}
189     * @return true if the given {@link TokenTypes#IDENT} has a valid parent token
190     */
191    private static boolean hasValidParentToken(DetailAST identifierAst) {
192        return !TokenUtil.isOfType(identifierAst.getParent(), INVALID_LAMBDA_PARAM_IDENT_PARENTS);
193    }
194
195    /**
196     * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator
197     * and is a candidate for lambda parameter.
198     *
199     * @param identAst token representing {@link TokenTypes#IDENT}
200     * @return true if the given {@link TokenTypes#IDENT} is a child of a dot operator
201     *     and a candidate for lambda parameter.
202     */
203    private static boolean isMethodInvocation(DetailAST identAst) {
204        final DetailAST parent = identAst.getParent();
205        return parent.getType() == TokenTypes.DOT
206                && identAst.equals(parent.getFirstChild());
207    }
208
209    /**
210     * Check if the given {@link TokenTypes#IDENT} is a left hand side value.
211     *
212     * @param identAst token representing {@link TokenTypes#IDENT}
213     * @return true if the given {@link TokenTypes#IDENT} is a left hand side value.
214     */
215    private static boolean isLeftHandOfAssignment(DetailAST identAst) {
216        final DetailAST parent = identAst.getParent();
217        return parent.getType() == TokenTypes.ASSIGN
218                && !identAst.equals(parent.getLastChild());
219    }
220
221    /**
222     * Maintains information about the lambda parameter.
223     */
224    private static final class LambdaParameterDetails {
225
226        /**
227         * Ast of type {@link TokenTypes#LAMBDA} enclosing the lambda
228         * parameter.
229         */
230        private final DetailAST enclosingLambda;
231
232        /**
233         * Ast of type {@link TokenTypes#IDENT} of the given
234         * lambda parameter.
235         */
236        private final DetailAST identifierAst;
237
238        /**
239         * Is the variable used.
240         */
241        private boolean used;
242
243        /**
244         * Create a new lambda parameter instance.
245         *
246         * @param enclosingLambda ast of type {@link TokenTypes#LAMBDA}
247         * @param identifierAst ast of type {@link TokenTypes#IDENT}
248         */
249        private LambdaParameterDetails(DetailAST enclosingLambda, DetailAST identifierAst) {
250            this.enclosingLambda = enclosingLambda;
251            this.identifierAst = identifierAst;
252        }
253
254        /**
255         * Register the lambda parameter as used.
256         */
257        private void registerAsUsed() {
258            used = true;
259        }
260
261        /**
262         * Get the name of the lambda parameter.
263         *
264         * @return the name of the lambda parameter
265         */
266        private String getName() {
267            return identifierAst.getText();
268        }
269
270        /**
271         * Get ast of type {@link TokenTypes#IDENT} of the given
272         * lambda parameter.
273         *
274         * @return ast of type {@link TokenTypes#IDENT} of the given lambda parameter
275         */
276        private DetailAST getIdentifierAst() {
277            return identifierAst;
278        }
279
280        /**
281         * Check if the lambda parameter is used.
282         *
283         * @return true if the lambda parameter is used
284         */
285        private boolean isUsed() {
286            return used;
287        }
288    }
289}