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.ArrayList; 024import java.util.Deque; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Optional; 028import java.util.Set; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 035 036/** 037 * <div> 038 * Checks that references to instance variables and methods of the current object 039 * avoid unnecessary use of {@code this}, unless it is required to resolve 040 * ambiguity with a shadowed field. 041 * </div> 042 * 043 * <p> 044 * By default, a redundant {@code this} is tolerated when the same statement, or the 045 * statement immediately before or after it, contains a {@code this} that is required 046 * to resolve a shadowed field. This keeps groups of assignments consistent, for example 047 * constructor bodies where only some parameters shadow fields. Set 048 * {@code allowAdjacentToRequiredThis} to {@code false} to report every redundant 049 * {@code this}. 050 * </p> 051 * 052 * @since 14.4.0 053 */ 054@FileStatefulCheck 055public class RedundantThisCheck extends AbstractCheck { 056 057 /** 058 * A key is pointing to the warning message text in "message.properties" file. 059 */ 060 public static final String MSG_KEY_FIELD = "redundant.this.field"; 061 062 /** 063 * A key is pointing to the warning message text in "message.properties" file. 064 */ 065 public static final String MSG_KEY_METHOD = "redundant.this.method"; 066 067 /** 068 * Tracks names (parameters and local variables) that can shadow a field. 069 */ 070 private final Deque<Set<String>> scopeStack = new ArrayDeque<>(); 071 072 /** 073 * Statements containing a {@code this} that is required to resolve shadowing. 074 */ 075 private final Set<DetailAST> requiredStatements = new HashSet<>(); 076 077 /** 078 * Redundant {@code this} usages whose reporting is decided in {@code finishTree}. 079 */ 080 private final List<RedundantUsage> redundantUsages = new ArrayList<>(); 081 082 /** 083 * Control to checking method calls. 084 */ 085 private boolean checkMethods; 086 087 /** 088 * Allow a redundant {@code this} when the same or an adjacent statement 089 * contains a required {@code this}. 090 */ 091 private boolean allowAdjacentToRequiredThis = true; 092 093 /** 094 * Creates a new {@code RedundantThisCheck} instance. 095 */ 096 public RedundantThisCheck() { 097 // no code by default 098 } 099 100 /** 101 * Setter to check whether to check redundant "this" with method call. 102 * 103 * @param checkMethods should we check method call 104 * @since 14.4.0 105 */ 106 public void setCheckMethods(boolean checkMethods) { 107 this.checkMethods = checkMethods; 108 } 109 110 /** 111 * Setter to allow redundant "this" when the same statement, or the statement 112 * immediately before or after it, contains a required "this". 113 * 114 * @param allowAdjacentToRequiredThis whether to allow such usages 115 * @since 14.4.0 116 */ 117 public void setAllowAdjacentToRequiredThis(boolean allowAdjacentToRequiredThis) { 118 this.allowAdjacentToRequiredThis = allowAdjacentToRequiredThis; 119 } 120 121 @Override 122 public int[] getRequiredTokens() { 123 return getAcceptableTokens(); 124 } 125 126 @Override 127 public int[] getDefaultTokens() { 128 return getRequiredTokens(); 129 } 130 131 @Override 132 public int[] getAcceptableTokens() { 133 return new int[] { 134 TokenTypes.METHOD_DEF, 135 TokenTypes.CTOR_DEF, 136 TokenTypes.LITERAL_CATCH, 137 TokenTypes.LITERAL_TRY, 138 TokenTypes.LITERAL_THIS, 139 TokenTypes.PATTERN_VARIABLE_DEF, 140 TokenTypes.SLIST, 141 TokenTypes.VARIABLE_DEF, 142 }; 143 } 144 145 @Override 146 public void beginTree(DetailAST rootAST) { 147 requiredStatements.clear(); 148 redundantUsages.clear(); 149 } 150 151 @Override 152 public void visitToken(DetailAST ast) { 153 switch (ast.getType()) { 154 case TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF -> { 155 scopeStack.push(new HashSet<>()); 156 addParametersToScope(ast.findFirstToken(TokenTypes.PARAMETERS)); 157 } 158 case TokenTypes.LITERAL_CATCH -> { 159 scopeStack.push(new HashSet<>()); 160 addParametersToScope(ast); 161 } 162 case TokenTypes.LITERAL_TRY -> { 163 scopeStack.push(new HashSet<>()); 164 final Optional<DetailAST> resourcesNode = Optional.of(ast.getFirstChild()) 165 .map(child -> child.findFirstToken(TokenTypes.RESOURCES)); 166 167 if (resourcesNode.isPresent()) { 168 addParametersToScope(resourcesNode.orElseThrow()); 169 } 170 } 171 case TokenTypes.SLIST -> scopeStack.push(new HashSet<>()); 172 case TokenTypes.VARIABLE_DEF, TokenTypes.PATTERN_VARIABLE_DEF -> { 173 final Set<String> currentScope = scopeStack.peek(); 174 if (currentScope != null) { 175 final String variable = ast.findFirstToken(TokenTypes.IDENT).getText(); 176 currentScope.add(variable); 177 } 178 } 179 default -> { 180 if (ast.getNextSibling() != null) { 181 checkUnnecessaryThis(ast); 182 } 183 } 184 } 185 } 186 187 @Override 188 public void leaveToken(DetailAST ast) { 189 if (TokenUtil.isOfType(ast, 190 TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, 191 TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_TRY, 192 TokenTypes.SLIST)) { 193 scopeStack.pop(); 194 } 195 } 196 197 @Override 198 public void finishTree(DetailAST rootAST) { 199 for (RedundantUsage usage : redundantUsages) { 200 if (!allowAdjacentToRequiredThis || !isNearRequiredThis(usage.statement())) { 201 log(usage.literalThis(), usage.messageKey(), usage.name()); 202 } 203 } 204 } 205 206 /** 207 * Classifies the use of "this" as required (shadowed) or redundant. Required 208 * usages are recorded by their enclosing statement; redundant usages are 209 * deferred so that {@code finishTree} can decide whether to report them. 210 * 211 * @param literalThis the {@code LITERAL_THIS} token to check 212 */ 213 private void checkUnnecessaryThis(DetailAST literalThis) { 214 final DetailAST parent = literalThis.getParent(); 215 if (parent.getType() == TokenTypes.DOT) { 216 final DetailAST grandParent = parent.getParent(); 217 final String name = literalThis.getNextSibling().getText(); 218 final DetailAST statement = findEnclosingStatement(literalThis); 219 220 if (grandParent.getType() == TokenTypes.METHOD_CALL) { 221 if (checkMethods) { 222 redundantUsages.add( 223 new RedundantUsage(literalThis, statement, MSG_KEY_METHOD, name)); 224 } 225 } 226 else if (isShadowedByLocalName(name)) { 227 if (statement != null) { 228 requiredStatements.add(statement); 229 } 230 } 231 else { 232 redundantUsages.add( 233 new RedundantUsage(literalThis, statement, MSG_KEY_FIELD, name)); 234 } 235 } 236 } 237 238 /** 239 * Finds the statement (a direct child of an {@code SLIST}) enclosing the given node. 240 * 241 * @param node the node 242 * @return the statement, or {@code null} if the node is not inside a block 243 * (for example, a field initializer) 244 */ 245 private static DetailAST findEnclosingStatement(DetailAST node) { 246 DetailAST statement = node; 247 while (!TokenUtil.isOfType(statement.getParent(), 248 TokenTypes.SLIST, TokenTypes.OBJBLOCK)) { 249 statement = statement.getParent(); 250 } 251 252 DetailAST result = null; 253 if (statement.getParent().getType() == TokenTypes.SLIST) { 254 result = statement; 255 } 256 257 return result; 258 } 259 260 /** 261 * Checks whether the given statement, or the statement immediately before or 262 * after it, contains a required {@code this}. 263 * 264 * @param statement the statement to check, may be {@code null} 265 * @return {@code true} if a required {@code this} is in the same or an adjacent statement 266 */ 267 private boolean isNearRequiredThis(DetailAST statement) { 268 return statement != null 269 && (requiredStatements.contains(statement) 270 || requiredStatements.contains(previousStatement(statement)) 271 || requiredStatements.contains(nextStatement(statement))); 272 } 273 274 /** 275 * Gets the statement before the given one, stepping over the {@code SEMI} 276 * that terminates the previous statement. 277 * 278 * @param statement the statement 279 * @return the previous statement, or {@code null} if there is none 280 */ 281 private static DetailAST previousStatement(DetailAST statement) { 282 DetailAST result = statement.getPreviousSibling(); 283 if (result != null && result.getType() == TokenTypes.SEMI) { 284 result = result.getPreviousSibling(); 285 } 286 return result; 287 } 288 289 /** 290 * Gets the statement after the given one, stepping over the {@code SEMI} 291 * that terminates the given statement. 292 * 293 * @param statement the statement 294 * @return the next statement, or {@code null} if there is none 295 */ 296 private static DetailAST nextStatement(DetailAST statement) { 297 DetailAST result = statement.getNextSibling(); 298 if (result != null && result.getType() == TokenTypes.SEMI) { 299 result = result.getNextSibling(); 300 } 301 return result; 302 } 303 304 /** 305 * Checks whether the given variable name is shadowed by any parameter or 306 * local variable currently in scope, meaning that {@code this.name} is 307 * necessary to distinguish the field from the shadowing name. 308 * 309 * @param name the variable name following "this" 310 * @return {@code true} if a shadowing name exists in any enclosing scope 311 */ 312 private boolean isShadowedByLocalName(String name) { 313 boolean result = false; 314 for (Set<String> scope : scopeStack) { 315 if (scope.contains(name)) { 316 result = true; 317 break; 318 } 319 } 320 return result; 321 } 322 323 /** 324 * Adds all parameters found within {@code parametersNode} to the current 325 * (top-of-stack) scope. {@code parametersNode} may be either a 326 * {@code PARAMETERS} node (for methods and constructors) or a 327 * {@code LITERAL_CATCH} node (for catch clauses); both carry their 328 * parameter definitions as direct {@code PARAMETER_DEF} children. 329 * 330 * @param parametersNode the node whose {@code PARAMETER_DEF} children 331 * should be added to the current scope 332 */ 333 private void addParametersToScope(DetailAST parametersNode) { 334 DetailAST child = parametersNode.getFirstChild(); 335 while (child != null) { 336 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT); 337 if (ident != null) { 338 scopeStack.peek().add(ident.getText()); 339 } 340 child = child.getNextSibling(); 341 } 342 } 343 344 /** 345 * A redundant {@code this} usage waiting for a reporting decision. 346 * 347 * @param literalThis the {@code LITERAL_THIS} token 348 * @param statement the enclosing statement, or {@code null} if outside a block 349 * @param messageKey the violation message key 350 * @param name the member name following "this" 351 */ 352 private record RedundantUsage( 353 DetailAST literalThis, DetailAST statement, String messageKey, String name) { 354 } 355 356}