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}