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.ArrayList; 023import java.util.Collection; 024import java.util.List; 025import java.util.Set; 026 027import javax.annotation.Nullable; 028 029import com.puppycrawl.tools.checkstyle.StatelessCheck; 030import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 031import com.puppycrawl.tools.checkstyle.api.DetailAST; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033 034/** 035 * <div> 036 * Checks that pattern variables that never have their values changed are declared final. 037 * </div> 038 * 039 * <p> 040 * Pattern variables are not implicitly final in Java, and they can be reassigned. 041 * This check verifies that pattern variables are explicitly declared as final if they 042 * are not reassigned within their flow scope. 043 * </p> 044 * 045 * @since 14.4.0 046 */ 047@StatelessCheck 048public class FinalPatternVariableCheck extends AbstractCheck { 049 050 /** 051 * A key is pointing to the warning message in "messages.properties" file. 052 */ 053 public static final String MSG_KEY = "final.pattern.variable"; 054 055 /** 056 * The set of all valid types of ASSIGN token for this check. 057 */ 058 private static final Set<Integer> ASSIGN_TOKEN_TYPES = Set.of( 059 TokenTypes.ASSIGN, TokenTypes.PLUS_ASSIGN, TokenTypes.MINUS_ASSIGN, 060 TokenTypes.STAR_ASSIGN, TokenTypes.DIV_ASSIGN, TokenTypes.MOD_ASSIGN, 061 TokenTypes.SR_ASSIGN, TokenTypes.BSR_ASSIGN, TokenTypes.SL_ASSIGN, 062 TokenTypes.BAND_ASSIGN, TokenTypes.BXOR_ASSIGN, TokenTypes.BOR_ASSIGN); 063 064 /** 065 * Creates a new {@code FinalPatternVariableCheck} instance. 066 */ 067 public FinalPatternVariableCheck() { 068 // no code by default 069 } 070 071 @Override 072 public int[] getRequiredTokens() { 073 return new int[] {TokenTypes.LITERAL_INSTANCEOF}; 074 } 075 076 @Override 077 public int[] getDefaultTokens() { 078 return getRequiredTokens(); 079 } 080 081 @Override 082 public int[] getAcceptableTokens() { 083 return getRequiredTokens(); 084 } 085 086 @Override 087 public void visitToken(final DetailAST ast) { 088 089 final List<DetailAST> patternVariableIdents = 090 getPatternVariableIdents(ast); 091 final List<DetailAST> reassignedVariableIdents = 092 getReassignedVariableIdents(ast); 093 094 for (DetailAST patternVariableIdent : patternVariableIdents) { 095 if (shouldBeFinal(patternVariableIdent, 096 reassignedVariableIdents)) { 097 log(patternVariableIdent, MSG_KEY, 098 patternVariableIdent.getText()); 099 } 100 } 101 } 102 103 /** 104 * Gets the list of all pattern variable idents in instanceof expression. 105 * 106 * @param ast ast tree of instanceof to get the list from. 107 * @return list of pattern variables. 108 */ 109 private static List<DetailAST> getPatternVariableIdents( 110 final DetailAST ast) { 111 112 final DetailAST outermostPatternVariable = 113 ast.findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF); 114 115 final List<DetailAST> patternVariableIdentsArray = new ArrayList<>(); 116 117 if (outermostPatternVariable != null) { 118 final DetailAST ident = 119 outermostPatternVariable.findFirstToken(TokenTypes.IDENT); 120 patternVariableIdentsArray.add(ident); 121 } 122 else { 123 final DetailAST recordPatternDef; 124 if (ast.getType() == TokenTypes.LITERAL_INSTANCEOF) { 125 recordPatternDef = 126 ast.findFirstToken(TokenTypes.RECORD_PATTERN_DEF); 127 } 128 else { 129 recordPatternDef = ast; 130 } 131 if (recordPatternDef != null) { 132 collectRecordPatternIdents(recordPatternDef, patternVariableIdentsArray); 133 } 134 } 135 return patternVariableIdentsArray; 136 } 137 138 /** 139 * Collects pattern variable identifiers from a record pattern AST definition. 140 * 141 * @param recordPatternDef AST of record pattern definition 142 * @param patternVariableIdentsArray collection to add pattern variable identifiers to 143 */ 144 private static void collectRecordPatternIdents( 145 final DetailAST recordPatternDef, 146 final Collection<DetailAST> patternVariableIdentsArray) { 147 final DetailAST recordPatternComponents = recordPatternDef 148 .findFirstToken(TokenTypes.RECORD_PATTERN_COMPONENTS); 149 150 if (recordPatternComponents != null) { 151 for (DetailAST innerPatternVariable = 152 recordPatternComponents.getFirstChild(); 153 innerPatternVariable != null; 154 innerPatternVariable = 155 innerPatternVariable.getNextSibling()) { 156 157 if (innerPatternVariable.getType() 158 == TokenTypes.PATTERN_VARIABLE_DEF) { 159 final DetailAST ident = 160 innerPatternVariable.findFirstToken( 161 TokenTypes.IDENT); 162 patternVariableIdentsArray.add(ident); 163 } 164 else { 165 patternVariableIdentsArray.addAll( 166 getPatternVariableIdents(innerPatternVariable)); 167 } 168 } 169 } 170 } 171 172 /** 173 * Gets the list of AST branches of reassigned variable identifiers. 174 * 175 * @param ast ast tree of checked instanceof statement 176 * @return list of AST identifiers that represent reassigned variables 177 */ 178 private static List<DetailAST> getReassignedVariableIdents( 179 final DetailAST ast) { 180 181 final List<DetailAST> reassignedVariableIdents = new ArrayList<>(); 182 final DetailAST scopeRoot = findReassignmentScopeRoot(ast); 183 184 if (scopeRoot != null) { 185 186 final List<DetailAST> branches = 187 expandReassignmentScopes(scopeRoot); 188 189 for (DetailAST branch : branches) { 190 for (DetailAST expressionBranch = branch; 191 expressionBranch != null; 192 expressionBranch = shiftToNextTraversedBranch( 193 expressionBranch, branch)) { 194 195 final DetailAST assignToken = 196 getMatchedAssignToken(expressionBranch); 197 198 if (assignToken != null) { 199 reassignedVariableIdents.add( 200 assignToken.getFirstChild()); 201 } 202 } 203 } 204 } 205 206 return reassignedVariableIdents; 207 } 208 209 /** 210 * Gets statements that follow conditional where pattern variable scope 211 * extends. Only returns top-level statements that are siblings of the 212 * conditional, excluding statements nested in control structures like while loops. 213 * 214 * @param conditionalStatement The if statement. 215 * @return List of statement nodes in the extended scope. 216 */ 217 private static List<DetailAST> getStatementsInExtendedScope( 218 final DetailAST conditionalStatement) { 219 final List<DetailAST> statements = new ArrayList<>(); 220 221 DetailAST nextSibling = conditionalStatement.getNextSibling(); 222 223 while (nextSibling != null) { 224 final int type = nextSibling.getType(); 225 if (type == TokenTypes.EXPR || type == TokenTypes.LITERAL_RETURN 226 || type == TokenTypes.LITERAL_IF) { 227 statements.add(nextSibling); 228 } 229 else { 230 if (type != TokenTypes.EMPTY_STAT) { 231 break; 232 } 233 } 234 nextSibling = nextSibling.getNextSibling(); 235 } 236 237 return statements; 238 } 239 240 /** 241 * Shifts once to the next possible branch within traverse trajectory. 242 * 243 * @param ast AST branch to shift from. 244 * @param boundAst AST Branch that traverse cannot extend to. 245 * @return next possible branch within traverse trajectory. 246 */ 247 @Nullable 248 private static DetailAST shiftToNextTraversedBranch( 249 final DetailAST ast, final DetailAST boundAst) { 250 DetailAST newAst = ast; 251 252 if (ast.getFirstChild() != null) { 253 newAst = ast.getFirstChild(); 254 } 255 else { 256 while (newAst.getNextSibling() == null 257 && !newAst.equals(boundAst)) { 258 newAst = newAst.getParent(); 259 } 260 if (newAst.equals(boundAst)) { 261 newAst = null; 262 } 263 else { 264 newAst = newAst.getNextSibling(); 265 } 266 } 267 268 return newAst; 269 } 270 271 /** 272 * Gets the type of ASSIGN tokens that matches with what follows the preceding branch. 273 * 274 * @param preAssignBranch branch preceding ASSIGN token types. 275 * @return type of ASSIGN token. 276 */ 277 @Nullable 278 private static DetailAST getMatchedAssignToken( 279 final DetailAST preAssignBranch) { 280 DetailAST matchedAssignToken = null; 281 282 for (int assignType : ASSIGN_TOKEN_TYPES) { 283 final DetailAST assignToken = 284 preAssignBranch.findFirstToken(assignType); 285 if (assignToken != null) { 286 matchedAssignToken = assignToken; 287 break; 288 } 289 } 290 291 return matchedAssignToken; 292 } 293 294 /** 295 * Checks whether a pattern variable should be declared final. 296 * 297 * @param patternVariableIdent AST ident of the pattern variable 298 * @param reassignedVariableIdents list of AST idents of reassigned vars 299 * @return true if pattern variable should be final, false otherwise 300 */ 301 private static boolean shouldBeFinal( 302 final DetailAST patternVariableIdent, 303 final Iterable<DetailAST> reassignedVariableIdents) { 304 305 final DetailAST patternVariableDef = patternVariableIdent.getParent(); 306 final DetailAST modifiers = 307 patternVariableDef.findFirstToken(TokenTypes.MODIFIERS); 308 final boolean isFinal = 309 modifiers.findFirstToken(TokenTypes.FINAL) != null; 310 boolean result = false; 311 312 if (!isFinal) { 313 boolean isReassigned = false; 314 for (DetailAST assignTokenIdent : reassignedVariableIdents) { 315 if (patternVariableIdent.getText().equals( 316 assignTokenIdent.getText())) { 317 isReassigned = true; 318 break; 319 } 320 } 321 result = !isReassigned; 322 } 323 324 return result; 325 } 326 327 /** 328 * Finds the nearest AST node that defines the scope in which reassignment 329 * of a pattern variable may occur. 330 * 331 * <p> 332 * The scope is determined by locating the closest enclosing conditional 333 * structure such as {@code if}, {@code else}, or ternary operator. 334 * </p> 335 * 336 * @param ast the AST node to start searching from 337 * @return the AST node representing the reassignment scope root, 338 * or {@code null} if none is found 339 */ 340 @Nullable 341 private static DetailAST findReassignmentScopeRoot(final DetailAST ast) { 342 343 DetailAST result = null; 344 345 for (DetailAST node = ast; node != null && result == null; 346 node = node.getParent()) { 347 348 final int type = node.getType(); 349 350 if (type == TokenTypes.LITERAL_IF 351 || type == TokenTypes.LITERAL_ELSE 352 || type == TokenTypes.QUESTION) { 353 result = node; 354 } 355 } 356 357 return result; 358 } 359 360 /** 361 * Expands the reassignment scope root into concrete AST branches 362 * that may contain reassigned pattern variables. 363 * 364 * <p> 365 * For ternary expressions, the conditional expression itself is 366 * treated as the reassignment scope. For {@code if} / {@code else} 367 * statements, the method includes the statement list and any 368 * subsequent statements where the pattern variable remains in scope. 369 * </p> 370 * 371 * @param scopeRoot the root AST node of the reassignment scope 372 * @return list of AST branches that may contain reassignments 373 */ 374 private static List<DetailAST> expandReassignmentScopes( 375 final DetailAST scopeRoot) { 376 377 final List<DetailAST> branches = new ArrayList<>(); 378 379 addBodyBranch(branches, scopeRoot); 380 branches.addAll(getStatementsInExtendedScope(scopeRoot)); 381 382 return branches; 383 } 384 385 /** 386 * Adds the body branch of a conditional (if/else) to the list. 387 * For braced blocks, adds the SLIST. For unbraced statements, 388 * adds the single statement directly. 389 * 390 * @param branches collection to add the body branch to 391 * @param scopeRoot the if/else AST node 392 */ 393 private static void addBodyBranch(final Collection<DetailAST> branches, 394 final DetailAST scopeRoot) { 395 if (scopeRoot.getType() == TokenTypes.LITERAL_IF) { 396 final DetailAST rparen = scopeRoot.findFirstToken(TokenTypes.RPAREN); 397 branches.add(rparen.getNextSibling()); 398 } 399 else { 400 branches.add(scopeRoot); 401 } 402 } 403 404}