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" file. 067 */ 068 public static final String MSG_UNUSED_LAMBDA_PARAMETER = "unused.lambda.parameter"; 069 070 /** 071 * Invalid parents of the lambda parameter identifier. 072 * These are tokens that can not be parents for a lambda parameter identifier. 073 */ 074 private static final int[] INVALID_LAMBDA_PARAM_IDENT_PARENTS = { 075 TokenTypes.DOT, 076 TokenTypes.LITERAL_NEW, 077 TokenTypes.METHOD_CALL, 078 TokenTypes.TYPE, 079 }; 080 081 /** 082 * Keeps track of the lambda parameters in a block. 083 */ 084 private final Deque<LambdaParameterDetails> lambdaParameters = new ArrayDeque<>(); 085 086 /** 087 * Creates a new {@code UnusedLambdaParameterShouldBeUnnamedCheck} instance. 088 */ 089 public UnusedLambdaParameterShouldBeUnnamedCheck() { 090 // no code by default 091 } 092 093 @Override 094 public int[] getDefaultTokens() { 095 return getRequiredTokens(); 096 } 097 098 @Override 099 public int[] getAcceptableTokens() { 100 return getRequiredTokens(); 101 } 102 103 @Override 104 public int[] getRequiredTokens() { 105 return new int[] { 106 TokenTypes.LAMBDA, 107 TokenTypes.IDENT, 108 }; 109 } 110 111 @Override 112 public void beginTree(DetailAST rootAST) { 113 lambdaParameters.clear(); 114 } 115 116 @Override 117 public void visitToken(DetailAST ast) { 118 if (ast.getType() == TokenTypes.LAMBDA) { 119 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS); 120 if (parameters != null) { 121 // we have multiple lambda parameters 122 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, parameter -> { 123 final DetailAST identifierAst = parameter.findFirstToken(TokenTypes.IDENT); 124 final LambdaParameterDetails lambdaParameter = 125 new LambdaParameterDetails(ast, identifierAst); 126 lambdaParameters.push(lambdaParameter); 127 }); 128 } 129 else if (ast.getChildCount() != 0) { 130 // we are not switch rule and have a single parameter 131 final LambdaParameterDetails lambdaParameter = 132 new LambdaParameterDetails(ast, ast.findFirstToken(TokenTypes.IDENT)); 133 lambdaParameters.push(lambdaParameter); 134 } 135 } 136 else if (isLambdaParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) { 137 // we do not count reassignment as usage 138 lambdaParameters.stream() 139 .filter(parameter -> parameter.getName().equals(ast.getText())) 140 .findFirst() 141 .ifPresent(LambdaParameterDetails::registerAsUsed); 142 } 143 } 144 145 @Override 146 public void leaveToken(DetailAST ast) { 147 while (lambdaParameters.peek() != null 148 && ast.equals(lambdaParameters.peek().enclosingLambda)) { 149 150 final Optional<LambdaParameterDetails> unusedLambdaParameter = 151 Optional.ofNullable(lambdaParameters.peek()) 152 .filter(parameter -> !parameter.isUsed()) 153 .filter(parameter -> !"_".equals(parameter.getName())); 154 155 unusedLambdaParameter.ifPresent(parameter -> { 156 log(parameter.getIdentifierAst(), 157 MSG_UNUSED_LAMBDA_PARAMETER, 158 parameter.getName()); 159 }); 160 lambdaParameters.pop(); 161 } 162 } 163 164 /** 165 * Visit ast of type {@link TokenTypes#IDENT} 166 * and check if it is a candidate for a lambda parameter identifier. 167 * 168 * @param identifierAst token representing {@code TokenTypes#IDENT} 169 * @return true if the given {@code TokenTypes#IDENT} could be a lambda parameter identifier 170 */ 171 private static boolean isLambdaParameterIdentifierCandidate(DetailAST identifierAst) { 172 // we should ignore the ident if it is in the lambda parameters declaration 173 final boolean isLambdaParameterDeclaration = 174 identifierAst.getParent().getType() == TokenTypes.LAMBDA 175 || identifierAst.getParent().getType() == TokenTypes.PARAMETER_DEF; 176 177 return !isLambdaParameterDeclaration 178 && (hasValidParentToken(identifierAst) || isMethodInvocation(identifierAst)); 179 } 180 181 /** 182 * Check if the given {@link TokenTypes#IDENT} has a valid parent token. 183 * A valid parent token is a token that can be a parent for a lambda parameter identifier. 184 * 185 * @param identifierAst token representing {@code TokenTypes#IDENT} 186 * @return true if the given {@code TokenTypes#IDENT} has a valid parent token 187 */ 188 private static boolean hasValidParentToken(DetailAST identifierAst) { 189 return !TokenUtil.isOfType(identifierAst.getParent(), INVALID_LAMBDA_PARAM_IDENT_PARENTS); 190 } 191 192 /** 193 * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator 194 * and is a candidate for lambda parameter. 195 * 196 * @param identAst token representing {@code TokenTypes#IDENT} 197 * @return true if the given {@code TokenTypes#IDENT} is a child of a dot operator 198 * and a candidate for lambda parameter. 199 */ 200 private static boolean isMethodInvocation(DetailAST identAst) { 201 final DetailAST parent = identAst.getParent(); 202 return parent.getType() == TokenTypes.DOT 203 && identAst.equals(parent.getFirstChild()); 204 } 205 206 /** 207 * Check if the given {@link TokenTypes#IDENT} is a left hand side value. 208 * 209 * @param identAst token representing {@code TokenTypes#IDENT} 210 * @return true if the given {@code TokenTypes#IDENT} is a left hand side value. 211 */ 212 private static boolean isLeftHandOfAssignment(DetailAST identAst) { 213 final DetailAST parent = identAst.getParent(); 214 return parent.getType() == TokenTypes.ASSIGN 215 && !identAst.equals(parent.getLastChild()); 216 } 217 218 /** 219 * Maintains information about the lambda parameter. 220 */ 221 private static final class LambdaParameterDetails { 222 223 /** 224 * Ast of type {@link TokenTypes#LAMBDA} enclosing the lambda parameter. 225 */ 226 private final DetailAST enclosingLambda; 227 228 /** 229 * Ast of type {@link TokenTypes#IDENT} of the given lambda parameter. 230 */ 231 private final DetailAST identifierAst; 232 233 /** 234 * Is the variable used. 235 */ 236 private boolean used; 237 238 /** 239 * Create a new lambda parameter instance. 240 * 241 * @param enclosingLambda ast of type {@link TokenTypes#LAMBDA} 242 * @param identifierAst ast of type {@link TokenTypes#IDENT} 243 */ 244 private LambdaParameterDetails(DetailAST enclosingLambda, DetailAST identifierAst) { 245 this.enclosingLambda = enclosingLambda; 246 this.identifierAst = identifierAst; 247 } 248 249 /** 250 * Register the lambda parameter as used. 251 */ 252 private void registerAsUsed() { 253 used = true; 254 } 255 256 /** 257 * Get the name of the lambda parameter. 258 * 259 * @return the name of the lambda parameter 260 */ 261 private String getName() { 262 return identifierAst.getText(); 263 } 264 265 /** 266 * Get ast of type {@link TokenTypes#IDENT} of the given lambda parameter. 267 * 268 * @return ast of type {@code TokenTypes#IDENT} of the given lambda parameter 269 */ 270 private DetailAST getIdentifierAst() { 271 return identifierAst; 272 } 273 274 /** 275 * Check if the lambda parameter is used. 276 * 277 * @return true if the lambda parameter is used 278 */ 279 private boolean isUsed() { 280 return used; 281 } 282 283 } 284 285}