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