1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.ArrayDeque;
23  import java.util.Collections;
24  import java.util.Deque;
25  import java.util.HashSet;
26  import java.util.Set;
27  
28  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
29  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
30  import com.puppycrawl.tools.checkstyle.api.DetailAST;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
33  
34  /**
35   * <p>
36   * Disallow assignment of parameters.
37   * </p>
38   * <p>
39   * Rationale:
40   * Parameter assignment is often considered poor
41   * programming practice. Forcing developers to declare
42   * parameters as final is often onerous. Having a check
43   * ensure that parameters are never assigned would give
44   * the best of both worlds.
45   * </p>
46   */
47  @FileStatefulCheck
48  public final class ParameterAssignmentCheck extends AbstractCheck {
49  
50      /**
51       * A key is pointing to the warning message text in "messages.properties"
52       * file.
53       */
54      public static final String MSG_KEY = "parameter.assignment";
55  
56      /** Stack of methods' parameters. */
57      private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>();
58      /** Current set of parameters. */
59      private Set<String> parameterNames;
60  
61      @Override
62      public int[] getDefaultTokens() {
63          return getRequiredTokens();
64      }
65  
66      @Override
67      public int[] getRequiredTokens() {
68          return new int[] {
69              TokenTypes.CTOR_DEF,
70              TokenTypes.METHOD_DEF,
71              TokenTypes.ASSIGN,
72              TokenTypes.PLUS_ASSIGN,
73              TokenTypes.MINUS_ASSIGN,
74              TokenTypes.STAR_ASSIGN,
75              TokenTypes.DIV_ASSIGN,
76              TokenTypes.MOD_ASSIGN,
77              TokenTypes.SR_ASSIGN,
78              TokenTypes.BSR_ASSIGN,
79              TokenTypes.SL_ASSIGN,
80              TokenTypes.BAND_ASSIGN,
81              TokenTypes.BXOR_ASSIGN,
82              TokenTypes.BOR_ASSIGN,
83              TokenTypes.INC,
84              TokenTypes.POST_INC,
85              TokenTypes.DEC,
86              TokenTypes.POST_DEC,
87          };
88      }
89  
90      @Override
91      public int[] getAcceptableTokens() {
92          return getRequiredTokens();
93      }
94  
95      @Override
96      public void beginTree(DetailAST rootAST) {
97          // clear data
98          parameterNamesStack.clear();
99          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 }