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