001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 @Override 089 public int[] getDefaultTokens() { 090 return getRequiredTokens(); 091 } 092 093 @Override 094 public int[] getAcceptableTokens() { 095 return getRequiredTokens(); 096 } 097 098 @Override 099 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}