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