001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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.Collections; 024import java.util.Deque; 025import java.util.HashSet; 026import java.util.Set; 027 028import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 033 034/** 035 * <p> 036 * Disallows assignment of parameters. 037 * </p> 038 * <p> 039 * Rationale: 040 * Parameter assignment is often considered poor 041 * programming practice. Forcing developers to declare 042 * parameters as final is often onerous. Having a check 043 * ensure that parameters are never assigned would give 044 * the best of both worlds. 045 * </p> 046 * <p> 047 * To configure the check: 048 * </p> 049 * <pre> 050 * <module name="ParameterAssignment"/> 051 * </pre> 052 * <p> 053 * Example: 054 * </p> 055 * <pre> 056 * class MyClass { 057 * int methodOne(int parameter) { 058 * if (parameter <= 0 ) { 059 * throw new IllegalArgumentException("A positive value is expected"); 060 * } 061 * parameter -= 2; // violation 062 * return parameter; 063 * } 064 * 065 * int methodTwo(int parameter) { 066 * if (parameter <= 0 ) { 067 * throw new IllegalArgumentException("A positive value is expected"); 068 * } 069 * int local = parameter; 070 * local -= 2; // OK 071 * return local; 072 * } 073 * } 074 * </pre> 075 * <p> 076 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 077 * </p> 078 * <p> 079 * Violation Message Keys: 080 * </p> 081 * <ul> 082 * <li> 083 * {@code parameter.assignment} 084 * </li> 085 * </ul> 086 * 087 * @since 3.2 088 */ 089@FileStatefulCheck 090public final class ParameterAssignmentCheck extends AbstractCheck { 091 092 /** 093 * A key is pointing to the warning message text in "messages.properties" 094 * file. 095 */ 096 public static final String MSG_KEY = "parameter.assignment"; 097 098 /** Stack of methods' parameters. */ 099 private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>(); 100 /** Current set of parameters. */ 101 private Set<String> parameterNames; 102 103 @Override 104 public int[] getDefaultTokens() { 105 return getRequiredTokens(); 106 } 107 108 @Override 109 public int[] getRequiredTokens() { 110 return new int[] { 111 TokenTypes.CTOR_DEF, 112 TokenTypes.METHOD_DEF, 113 TokenTypes.ASSIGN, 114 TokenTypes.PLUS_ASSIGN, 115 TokenTypes.MINUS_ASSIGN, 116 TokenTypes.STAR_ASSIGN, 117 TokenTypes.DIV_ASSIGN, 118 TokenTypes.MOD_ASSIGN, 119 TokenTypes.SR_ASSIGN, 120 TokenTypes.BSR_ASSIGN, 121 TokenTypes.SL_ASSIGN, 122 TokenTypes.BAND_ASSIGN, 123 TokenTypes.BXOR_ASSIGN, 124 TokenTypes.BOR_ASSIGN, 125 TokenTypes.INC, 126 TokenTypes.POST_INC, 127 TokenTypes.DEC, 128 TokenTypes.POST_DEC, 129 }; 130 } 131 132 @Override 133 public int[] getAcceptableTokens() { 134 return getRequiredTokens(); 135 } 136 137 @Override 138 public void beginTree(DetailAST rootAST) { 139 // clear data 140 parameterNamesStack.clear(); 141 parameterNames = Collections.emptySet(); 142 } 143 144 @Override 145 public void visitToken(DetailAST ast) { 146 switch (ast.getType()) { 147 case TokenTypes.CTOR_DEF: 148 case TokenTypes.METHOD_DEF: 149 visitMethodDef(ast); 150 break; 151 case TokenTypes.ASSIGN: 152 case TokenTypes.PLUS_ASSIGN: 153 case TokenTypes.MINUS_ASSIGN: 154 case TokenTypes.STAR_ASSIGN: 155 case TokenTypes.DIV_ASSIGN: 156 case TokenTypes.MOD_ASSIGN: 157 case TokenTypes.SR_ASSIGN: 158 case TokenTypes.BSR_ASSIGN: 159 case TokenTypes.SL_ASSIGN: 160 case TokenTypes.BAND_ASSIGN: 161 case TokenTypes.BXOR_ASSIGN: 162 case TokenTypes.BOR_ASSIGN: 163 visitAssign(ast); 164 break; 165 case TokenTypes.INC: 166 case TokenTypes.POST_INC: 167 case TokenTypes.DEC: 168 case TokenTypes.POST_DEC: 169 visitIncDec(ast); 170 break; 171 default: 172 throw new IllegalStateException(ast.toString()); 173 } 174 } 175 176 @Override 177 public void leaveToken(DetailAST ast) { 178 switch (ast.getType()) { 179 case TokenTypes.CTOR_DEF: 180 case TokenTypes.METHOD_DEF: 181 leaveMethodDef(); 182 break; 183 case TokenTypes.ASSIGN: 184 case TokenTypes.PLUS_ASSIGN: 185 case TokenTypes.MINUS_ASSIGN: 186 case TokenTypes.STAR_ASSIGN: 187 case TokenTypes.DIV_ASSIGN: 188 case TokenTypes.MOD_ASSIGN: 189 case TokenTypes.SR_ASSIGN: 190 case TokenTypes.BSR_ASSIGN: 191 case TokenTypes.SL_ASSIGN: 192 case TokenTypes.BAND_ASSIGN: 193 case TokenTypes.BXOR_ASSIGN: 194 case TokenTypes.BOR_ASSIGN: 195 case TokenTypes.INC: 196 case TokenTypes.POST_INC: 197 case TokenTypes.DEC: 198 case TokenTypes.POST_DEC: 199 // Do nothing 200 break; 201 default: 202 throw new IllegalStateException(ast.toString()); 203 } 204 } 205 206 /** 207 * Checks if this is assignments of parameter. 208 * 209 * @param ast assignment to check. 210 */ 211 private void visitAssign(DetailAST ast) { 212 checkIdent(ast); 213 } 214 215 /** 216 * Checks if this is increment/decrement of parameter. 217 * 218 * @param ast dec/inc to check. 219 */ 220 private void visitIncDec(DetailAST ast) { 221 checkIdent(ast); 222 } 223 224 /** 225 * Check if ident is parameter. 226 * 227 * @param ast ident to check. 228 */ 229 private void checkIdent(DetailAST ast) { 230 final DetailAST identAST = ast.getFirstChild(); 231 232 if (identAST != null 233 && identAST.getType() == TokenTypes.IDENT 234 && parameterNames.contains(identAST.getText())) { 235 log(ast, MSG_KEY, identAST.getText()); 236 } 237 } 238 239 /** 240 * Creates new set of parameters and store old one in stack. 241 * 242 * @param ast a method to process. 243 */ 244 private void visitMethodDef(DetailAST ast) { 245 parameterNamesStack.push(parameterNames); 246 parameterNames = new HashSet<>(); 247 248 visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS)); 249 } 250 251 /** Restores old set of parameters. */ 252 private void leaveMethodDef() { 253 parameterNames = parameterNamesStack.pop(); 254 } 255 256 /** 257 * Creates new parameter set for given method. 258 * 259 * @param ast a method for process. 260 */ 261 private void visitMethodParameters(DetailAST ast) { 262 DetailAST parameterDefAST = 263 ast.findFirstToken(TokenTypes.PARAMETER_DEF); 264 265 while (parameterDefAST != null) { 266 if (parameterDefAST.getType() == TokenTypes.PARAMETER_DEF 267 && !CheckUtil.isReceiverParameter(parameterDefAST)) { 268 final DetailAST param = 269 parameterDefAST.findFirstToken(TokenTypes.IDENT); 270 parameterNames.add(param.getText()); 271 } 272 parameterDefAST = parameterDefAST.getNextSibling(); 273 } 274 } 275 276}