View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
34  
35  /**
36   * <div>
37   * Disallows assignment of parameters.
38   * </div>
39   *
40   * <p>
41   * Rationale:
42   * Parameter assignment is often considered poor
43   * programming practice. Forcing developers to declare
44   * parameters as final is often onerous. Having a check
45   * ensure that parameters are never assigned would give
46   * the best of both worlds.
47   * </p>
48   *
49   * <p>
50   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
51   * </p>
52   *
53   * <p>
54   * Violation Message Keys:
55   * </p>
56   * <ul>
57   * <li>
58   * {@code parameter.assignment}
59   * </li>
60   * </ul>
61   *
62   * @since 3.2
63   */
64  @FileStatefulCheck
65  public final class ParameterAssignmentCheck extends AbstractCheck {
66  
67      /**
68       * A key is pointing to the warning message text in "messages.properties"
69       * file.
70       */
71      public static final String MSG_KEY = "parameter.assignment";
72  
73      /** Stack of methods' parameters. */
74      private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>();
75      /** Current set of parameters. */
76      private Set<String> parameterNames;
77  
78      @Override
79      public int[] getDefaultTokens() {
80          return getRequiredTokens();
81      }
82  
83      @Override
84      public int[] getRequiredTokens() {
85          return new int[] {
86              TokenTypes.CTOR_DEF,
87              TokenTypes.METHOD_DEF,
88              TokenTypes.ASSIGN,
89              TokenTypes.PLUS_ASSIGN,
90              TokenTypes.MINUS_ASSIGN,
91              TokenTypes.STAR_ASSIGN,
92              TokenTypes.DIV_ASSIGN,
93              TokenTypes.MOD_ASSIGN,
94              TokenTypes.SR_ASSIGN,
95              TokenTypes.BSR_ASSIGN,
96              TokenTypes.SL_ASSIGN,
97              TokenTypes.BAND_ASSIGN,
98              TokenTypes.BXOR_ASSIGN,
99              TokenTypes.BOR_ASSIGN,
100             TokenTypes.INC,
101             TokenTypes.POST_INC,
102             TokenTypes.DEC,
103             TokenTypes.POST_DEC,
104             TokenTypes.LAMBDA,
105         };
106     }
107 
108     @Override
109     public int[] getAcceptableTokens() {
110         return getRequiredTokens();
111     }
112 
113     @Override
114     public void beginTree(DetailAST rootAST) {
115         // clear data
116         parameterNamesStack.clear();
117         parameterNames = Collections.emptySet();
118     }
119 
120     @Override
121     public void visitToken(DetailAST ast) {
122         final int type = ast.getType();
123         if (TokenUtil.isOfType(type, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)) {
124             visitMethodDef(ast);
125         }
126         else if (type == TokenTypes.LAMBDA) {
127             if (ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
128                 visitLambda(ast);
129             }
130         }
131         else {
132             checkNestedIdent(ast);
133         }
134     }
135 
136     @Override
137     public void leaveToken(DetailAST ast) {
138         final int type = ast.getType();
139         if (TokenUtil.isOfType(type, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)
140                 || type == TokenTypes.LAMBDA
141                 && ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
142             parameterNames = parameterNamesStack.pop();
143         }
144     }
145 
146     /**
147      * Check if nested ident is parameter.
148      *
149      * @param ast parent of node of ident
150      */
151     private void checkNestedIdent(DetailAST ast) {
152         final DetailAST identAST = ast.getFirstChild();
153 
154         if (identAST != null
155             && identAST.getType() == TokenTypes.IDENT
156             && parameterNames.contains(identAST.getText())) {
157             log(ast, MSG_KEY, identAST.getText());
158         }
159     }
160 
161     /**
162      * Creates new set of parameters and store old one in stack.
163      *
164      * @param ast a method to process.
165      */
166     private void visitMethodDef(DetailAST ast) {
167         parameterNamesStack.push(parameterNames);
168         parameterNames = new HashSet<>();
169 
170         visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS));
171     }
172 
173     /**
174      * Creates new set of parameters and store old one in stack.
175      *
176      * @param lambdaAst node of type {@link TokenTypes#LAMBDA}.
177      */
178     private void visitLambda(DetailAST lambdaAst) {
179         parameterNamesStack.push(parameterNames);
180         parameterNames = new HashSet<>();
181 
182         DetailAST parameterAst = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
183         if (parameterAst == null) {
184             parameterAst = lambdaAst.getFirstChild();
185         }
186         visitLambdaParameters(parameterAst);
187     }
188 
189     /**
190      * Creates new parameter set for given method.
191      *
192      * @param ast a method for process.
193      */
194     private void visitMethodParameters(DetailAST ast) {
195         visitParameters(ast);
196     }
197 
198     /**
199      * Creates new parameter set for given lambda expression.
200      *
201      * @param ast a lambda expression parameter to process
202      */
203     private void visitLambdaParameters(DetailAST ast) {
204         if (ast.getType() == TokenTypes.IDENT) {
205             parameterNames.add(ast.getText());
206         }
207         else {
208             visitParameters(ast);
209         }
210     }
211 
212     /**
213      * Visits parameter list and adds parameter names to the set.
214      *
215      * @param parametersAst ast node of type {@link TokenTypes#PARAMETERS}.
216      */
217     private void visitParameters(DetailAST parametersAst) {
218         DetailAST parameterDefAST =
219             parametersAst.findFirstToken(TokenTypes.PARAMETER_DEF);
220 
221         while (parameterDefAST != null) {
222             if (!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 }