001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * @since 10.18.0 061 */ 062 063@FileStatefulCheck 064public class UnusedCatchParameterShouldBeUnnamedCheck extends AbstractCheck { 065 066 /** 067 * A key is pointing to the warning message text in "messages.properties" 068 * file. 069 */ 070 public static final String MSG_UNUSED_CATCH_PARAMETER = "unused.catch.parameter"; 071 072 /** 073 * Invalid parents of the catch parameter identifier. 074 */ 075 private static final int[] INVALID_CATCH_PARAM_IDENT_PARENTS = { 076 TokenTypes.DOT, 077 TokenTypes.LITERAL_NEW, 078 TokenTypes.METHOD_CALL, 079 TokenTypes.TYPE, 080 }; 081 082 /** 083 * Keeps track of the catch parameters in a block. 084 */ 085 private final Deque<CatchParameterDetails> catchParameters = new ArrayDeque<>(); 086 087 @Override 088 public int[] getDefaultTokens() { 089 return getRequiredTokens(); 090 } 091 092 @Override 093 public int[] getAcceptableTokens() { 094 return getRequiredTokens(); 095 } 096 097 @Override 098 public int[] getRequiredTokens() { 099 return new int[] { 100 TokenTypes.LITERAL_CATCH, 101 TokenTypes.IDENT, 102 }; 103 } 104 105 @Override 106 public void beginTree(DetailAST rootAST) { 107 catchParameters.clear(); 108 } 109 110 @Override 111 public void visitToken(DetailAST ast) { 112 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 113 final CatchParameterDetails catchParameter = new CatchParameterDetails(ast); 114 catchParameters.push(catchParameter); 115 } 116 else if (isCatchParameterIdentifierCandidate(ast) && !isLeftHandOfAssignment(ast)) { 117 // we do not count reassignment as usage 118 catchParameters.stream() 119 .filter(parameter -> parameter.getName().equals(ast.getText())) 120 .findFirst() 121 .ifPresent(CatchParameterDetails::registerAsUsed); 122 } 123 } 124 125 @Override 126 public void leaveToken(DetailAST ast) { 127 if (ast.getType() == TokenTypes.LITERAL_CATCH) { 128 final Optional<CatchParameterDetails> unusedCatchParameter = 129 Optional.ofNullable(catchParameters.peek()) 130 .filter(parameter -> !parameter.isUsed()) 131 .filter(parameter -> !"_".equals(parameter.getName())); 132 133 unusedCatchParameter.ifPresent(parameter -> { 134 log(parameter.getParameterDefinition(), 135 MSG_UNUSED_CATCH_PARAMETER, 136 parameter.getName()); 137 }); 138 catchParameters.pop(); 139 } 140 } 141 142 /** 143 * Visit ast of type {@link TokenTypes#IDENT} 144 * and check if it is a candidate for a catch parameter identifier. 145 * 146 * @param identifierAst token representing {@link TokenTypes#IDENT} 147 * @return true if the given {@link TokenTypes#IDENT} could be a catch parameter identifier 148 */ 149 private static boolean isCatchParameterIdentifierCandidate(DetailAST identifierAst) { 150 // we should ignore the ident if it is in the exception declaration 151 return identifierAst.getParent().getParent().getType() != TokenTypes.LITERAL_CATCH 152 && (!TokenUtil.isOfType(identifierAst.getParent(), INVALID_CATCH_PARAM_IDENT_PARENTS) 153 || isMethodInvocation(identifierAst)); 154 } 155 156 /** 157 * Check if the given {@link TokenTypes#IDENT} is a child of a dot operator 158 * and is a candidate for catch parameter. 159 * 160 * @param identAst token representing {@link TokenTypes#IDENT} 161 * @return true if the given {@link TokenTypes#IDENT} is a child of a dot operator 162 * and a candidate for catch parameter. 163 */ 164 private static boolean isMethodInvocation(DetailAST identAst) { 165 final DetailAST parent = identAst.getParent(); 166 return parent.getType() == TokenTypes.DOT 167 && identAst.equals(parent.getFirstChild()); 168 } 169 170 /** 171 * Check if the given {@link TokenTypes#IDENT} is a left hand side value. 172 * 173 * @param identAst token representing {@link TokenTypes#IDENT} 174 * @return true if the given {@link TokenTypes#IDENT} is a left hand side value. 175 */ 176 private static boolean isLeftHandOfAssignment(DetailAST identAst) { 177 final DetailAST parent = identAst.getParent(); 178 return parent.getType() == TokenTypes.ASSIGN 179 && !identAst.equals(parent.getLastChild()); 180 } 181 182 /** 183 * Maintains information about the catch parameter. 184 */ 185 private static final class CatchParameterDetails { 186 187 /** 188 * The name of the catch parameter. 189 */ 190 private final String name; 191 192 /** 193 * Ast of type {@link TokenTypes#PARAMETER_DEF} to use it when logging. 194 */ 195 private final DetailAST parameterDefinition; 196 197 /** 198 * Is the variable used. 199 */ 200 private boolean used; 201 202 /** 203 * Create a new catch parameter instance. 204 * 205 * @param enclosingCatchClause ast of type {@link TokenTypes#LITERAL_CATCH} 206 */ 207 private CatchParameterDetails(DetailAST enclosingCatchClause) { 208 parameterDefinition = 209 enclosingCatchClause.findFirstToken(TokenTypes.PARAMETER_DEF); 210 name = parameterDefinition.findFirstToken(TokenTypes.IDENT).getText(); 211 } 212 213 /** 214 * Register the catch parameter as used. 215 */ 216 private void registerAsUsed() { 217 used = true; 218 } 219 220 /** 221 * Get the name of the catch parameter. 222 * 223 * @return the name of the catch parameter 224 */ 225 private String getName() { 226 return name; 227 } 228 229 /** 230 * Check if the catch parameter is used. 231 * 232 * @return true if the catch parameter is used 233 */ 234 private boolean isUsed() { 235 return used; 236 } 237 238 /** 239 * Get the parameter definition token of the catch parameter 240 * represented by ast of type {@link TokenTypes#PARAMETER_DEF}. 241 * 242 * @return the ast of type {@link TokenTypes#PARAMETER_DEF} 243 */ 244 private DetailAST getParameterDefinition() { 245 return parameterDefinition; 246 } 247 } 248}