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