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