001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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 * 053 * @since 3.2 054 */ 055@FileStatefulCheck 056public final class ParameterAssignmentCheck extends AbstractCheck { 057 058 /** 059 * A key is pointing to the warning message text in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_KEY = "parameter.assignment"; 063 064 /** Stack of methods' parameters. */ 065 private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>(); 066 /** Current set of parameters. */ 067 private Set<String> parameterNames; 068 069 @Override 070 public int[] getDefaultTokens() { 071 return getRequiredTokens(); 072 } 073 074 @Override 075 public int[] getRequiredTokens() { 076 return new int[] { 077 TokenTypes.CTOR_DEF, 078 TokenTypes.METHOD_DEF, 079 TokenTypes.ASSIGN, 080 TokenTypes.PLUS_ASSIGN, 081 TokenTypes.MINUS_ASSIGN, 082 TokenTypes.STAR_ASSIGN, 083 TokenTypes.DIV_ASSIGN, 084 TokenTypes.MOD_ASSIGN, 085 TokenTypes.SR_ASSIGN, 086 TokenTypes.BSR_ASSIGN, 087 TokenTypes.SL_ASSIGN, 088 TokenTypes.BAND_ASSIGN, 089 TokenTypes.BXOR_ASSIGN, 090 TokenTypes.BOR_ASSIGN, 091 TokenTypes.INC, 092 TokenTypes.POST_INC, 093 TokenTypes.DEC, 094 TokenTypes.POST_DEC, 095 }; 096 } 097 098 @Override 099 public int[] getAcceptableTokens() { 100 return getRequiredTokens(); 101 } 102 103 @Override 104 public void beginTree(DetailAST rootAST) { 105 // clear data 106 parameterNamesStack.clear(); 107 parameterNames = Collections.emptySet(); 108 } 109 110 @Override 111 public void visitToken(DetailAST ast) { 112 switch (ast.getType()) { 113 case TokenTypes.CTOR_DEF: 114 case TokenTypes.METHOD_DEF: 115 visitMethodDef(ast); 116 break; 117 case TokenTypes.ASSIGN: 118 case TokenTypes.PLUS_ASSIGN: 119 case TokenTypes.MINUS_ASSIGN: 120 case TokenTypes.STAR_ASSIGN: 121 case TokenTypes.DIV_ASSIGN: 122 case TokenTypes.MOD_ASSIGN: 123 case TokenTypes.SR_ASSIGN: 124 case TokenTypes.BSR_ASSIGN: 125 case TokenTypes.SL_ASSIGN: 126 case TokenTypes.BAND_ASSIGN: 127 case TokenTypes.BXOR_ASSIGN: 128 case TokenTypes.BOR_ASSIGN: 129 visitAssign(ast); 130 break; 131 case TokenTypes.INC: 132 case TokenTypes.POST_INC: 133 case TokenTypes.DEC: 134 case TokenTypes.POST_DEC: 135 visitIncDec(ast); 136 break; 137 default: 138 throw new IllegalStateException(ast.toString()); 139 } 140 } 141 142 @Override 143 public void leaveToken(DetailAST ast) { 144 switch (ast.getType()) { 145 case TokenTypes.CTOR_DEF: 146 case TokenTypes.METHOD_DEF: 147 leaveMethodDef(); 148 break; 149 case TokenTypes.ASSIGN: 150 case TokenTypes.PLUS_ASSIGN: 151 case TokenTypes.MINUS_ASSIGN: 152 case TokenTypes.STAR_ASSIGN: 153 case TokenTypes.DIV_ASSIGN: 154 case TokenTypes.MOD_ASSIGN: 155 case TokenTypes.SR_ASSIGN: 156 case TokenTypes.BSR_ASSIGN: 157 case TokenTypes.SL_ASSIGN: 158 case TokenTypes.BAND_ASSIGN: 159 case TokenTypes.BXOR_ASSIGN: 160 case TokenTypes.BOR_ASSIGN: 161 case TokenTypes.INC: 162 case TokenTypes.POST_INC: 163 case TokenTypes.DEC: 164 case TokenTypes.POST_DEC: 165 // Do nothing 166 break; 167 default: 168 throw new IllegalStateException(ast.toString()); 169 } 170 } 171 172 /** 173 * Checks if this is assignments of parameter. 174 * @param ast assignment to check. 175 */ 176 private void visitAssign(DetailAST ast) { 177 checkIdent(ast); 178 } 179 180 /** 181 * Checks if this is increment/decrement of parameter. 182 * @param ast dec/inc to check. 183 */ 184 private void visitIncDec(DetailAST ast) { 185 checkIdent(ast); 186 } 187 188 /** 189 * Check if ident is parameter. 190 * @param ast ident to check. 191 */ 192 private void checkIdent(DetailAST ast) { 193 final DetailAST identAST = ast.getFirstChild(); 194 195 if (identAST != null 196 && identAST.getType() == TokenTypes.IDENT 197 && parameterNames.contains(identAST.getText())) { 198 log(ast, MSG_KEY, identAST.getText()); 199 } 200 } 201 202 /** 203 * Creates new set of parameters and store old one in stack. 204 * @param ast a method to process. 205 */ 206 private void visitMethodDef(DetailAST ast) { 207 parameterNamesStack.push(parameterNames); 208 parameterNames = new HashSet<>(); 209 210 visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS)); 211 } 212 213 /** Restores old set of parameters. */ 214 private void leaveMethodDef() { 215 parameterNames = parameterNamesStack.pop(); 216 } 217 218 /** 219 * Creates new parameter set for given method. 220 * @param ast a method for process. 221 */ 222 private void visitMethodParameters(DetailAST ast) { 223 DetailAST parameterDefAST = 224 ast.findFirstToken(TokenTypes.PARAMETER_DEF); 225 226 while (parameterDefAST != null) { 227 if (parameterDefAST.getType() == TokenTypes.PARAMETER_DEF 228 && !CheckUtil.isReceiverParameter(parameterDefAST)) { 229 final DetailAST param = 230 parameterDefAST.findFirstToken(TokenTypes.IDENT); 231 parameterNames.add(param.getText()); 232 } 233 parameterDefAST = parameterDefAST.getNextSibling(); 234 } 235 } 236 237}