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 catch 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>: This check should be activated only on source code 056 * that is compiled by jdk21 or higher; 057 * unnamed catch parameters came out as the first preview in Java 21. 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.catch.parameter} 070 * </li> 071 * </ul> 072 * 073 * @since 10.18.0 074 * 075 */ 076 077@FileStatefulCheck 078public class UnusedCatchParameterShouldBeUnnamedCheck extends AbstractCheck { 079 080 /** 081 * A key is pointing to the warning message text in "messages.properties" 082 * file. 083 */ 084 public static final String MSG_UNUSED_CATCH_PARAMETER = "unused.catch.parameter"; 085 086 /** 087 * Invalid parents of the catch parameter identifier. 088 */ 089 private static final int[] INVALID_CATCH_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 catch parameters in a block. 098 */ 099 private final Deque<CatchParameterDetails> catchParameters = 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.LITERAL_CATCH, 115 TokenTypes.IDENT, 116 }; 117 } 118 119 @Override 120 public void beginTree(DetailAST rootAST) { 121 catchParameters.clear(); 122 } 123 124 @Override 125 public void visitToken(DetailAST ast) { 126 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 127 final CatchParameterDetails catchParameter = new CatchParameterDetails(ast); 128 catchParameters.push(catchParameter); 129 } 130 else if (isCatchParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) { 131 // we do not count reassignment as usage 132 catchParameters.stream() 133 .filter(parameter -> parameter.getName().equals(ast.getText())) 134 .findFirst() 135 .ifPresent(CatchParameterDetails::registerAsUsed); 136 } 137 } 138 139 @Override 140 public void leaveToken(DetailAST ast) { 141 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 142 final Optional<CatchParameterDetails> unusedCatchParameter = 143 Optional.ofNullable(catchParameters.peek()) 144 .filter(parameter -> !parameter.isUsed()) 145 .filter(parameter -> !"_".equals(parameter.getName())); 146 147 unusedCatchParameter.ifPresent(parameter -> { 148 log(parameter.getParameterDefinition(), 149 MSG_UNUSED_CATCH_PARAMETER, 150 parameter.getName()); 151 }); 152 catchParameters.pop(); 153 } 154 } 155 156 /** 157 * Visit ast of type {@link TokenTypes#IDENT} 158 * and check if it is a candidate for a catch parameter identifier. 159 * 160 * @param identifierAst token representing {@link TokenTypes#IDENT} 161 * @return true if the given {@link TokenTypes#IDENT} could be a catch parameter identifier 162 */ 163 private static boolean isCatchParameterIdentifierCandidate(DetailAST identifierAst) { 164 // we should ignore the ident if it is in the exception declaration 165 return identifierAst.getParent().getParent().getType() != TokenTypes.LITERAL_CATCH 166 && (!TokenUtil.isOfType(identifierAst.getParent(), INVALID_CATCH_PARAM_IDENT_PARENTS) 167 || isMethodInvocation(identifierAst)); 168 } 169 170 /** 171 * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator 172 * and is a candidate for catch parameter. 173 * 174 * @param identAst token representing {@link TokenTypes#IDENT} 175 * @return true if the given {@link TokenTypes#IDENT} is a child of a dot operator 176 * and a candidate for catch parameter. 177 */ 178 private static boolean isMethodInvocation(DetailAST identAst) { 179 final DetailAST parent = identAst.getParent(); 180 return parent.getType() == TokenTypes.DOT 181 && identAst.equals(parent.getFirstChild()); 182 } 183 184 /** 185 * Check if the given {@link TokenTypes#IDENT} is a left hand side value. 186 * 187 * @param identAst token representing {@link TokenTypes#IDENT} 188 * @return true if the given {@link TokenTypes#IDENT} is a left hand side value. 189 */ 190 private static boolean isLeftHandOfAssignment(DetailAST identAst) { 191 final DetailAST parent = identAst.getParent(); 192 return parent.getType() == TokenTypes.ASSIGN 193 && !identAst.equals(parent.getLastChild()); 194 } 195 196 /** 197 * Maintains information about the catch parameter. 198 */ 199 private static final class CatchParameterDetails { 200 201 /** 202 * The name of the catch parameter. 203 */ 204 private final String name; 205 206 /** 207 * Ast of type {@link TokenTypes#PARAMETER_DEF} to use it when logging. 208 */ 209 private final DetailAST parameterDefinition; 210 211 /** 212 * Is the variable used. 213 */ 214 private boolean used; 215 216 /** 217 * Create a new catch parameter instance. 218 * 219 * @param enclosingCatchClause ast of type {@link TokenTypes#LITERAL_CATCH} 220 */ 221 private CatchParameterDetails(DetailAST enclosingCatchClause) { 222 parameterDefinition = 223 enclosingCatchClause.findFirstToken(TokenTypes.PARAMETER_DEF); 224 name = parameterDefinition.findFirstToken(TokenTypes.IDENT).getText(); 225 } 226 227 /** 228 * Register the catch parameter as used. 229 */ 230 private void registerAsUsed() { 231 used = true; 232 } 233 234 /** 235 * Get the name of the catch parameter. 236 * 237 * @return the name of the catch parameter 238 */ 239 private String getName() { 240 return name; 241 } 242 243 /** 244 * Check if the catch parameter is used. 245 * 246 * @return true if the catch parameter is used 247 */ 248 private boolean isUsed() { 249 return used; 250 } 251 252 /** 253 * Get the parameter definition token of the catch parameter 254 * represented by ast of type {@link TokenTypes#PARAMETER_DEF}. 255 * 256 * @return the ast of type {@link TokenTypes#PARAMETER_DEF} 257 */ 258 private DetailAST getParameterDefinition() { 259 return parameterDefinition; 260 } 261 } 262}