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