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