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}