001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2022 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;
033import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
034
035/**
036 * <p>
037 * Disallows assignment of parameters.
038 * </p>
039 * <p>
040 * Rationale:
041 * Parameter assignment is often considered poor
042 * programming practice. Forcing developers to declare
043 * parameters as final is often onerous. Having a check
044 * ensure that parameters are never assigned would give
045 * the best of both worlds.
046 * </p>
047 * <p>
048 * To configure the check:
049 * </p>
050 * <pre>
051 * &lt;module name=&quot;ParameterAssignment&quot;/&gt;
052 * </pre>
053 * <p>
054 * Example:
055 * </p>
056 * <pre>
057 * class MyClass {
058 *   int methodOne(int parameter) {
059 *     if (parameter &lt;= 0 ) {
060 *       throw new IllegalArgumentException("A positive value is expected");
061 *     }
062 *     parameter -= 2;  // violation
063 *     return parameter;
064 *   }
065 *
066 *   int methodTwo(int parameter) {
067 *     if (parameter &lt;= 0 ) {
068 *       throw new IllegalArgumentException("A positive value is expected");
069 *     }
070 *     int local = parameter;
071 *     local -= 2;  // OK
072 *     return local;
073 *   }
074 *
075 *   IntPredicate obj = a -&gt; ++a == 12; // violation
076 *   IntBinaryOperator obj2 = (int a, int b) -&gt; {
077 *       a++;     // violation
078 *       b += 12; // violation
079 *       return a + b;
080 *   };
081 *   IntPredicate obj3 = a -&gt; {
082 *       int b = a; // ok
083 *       return ++b == 12;
084 *   };
085 * }
086 * </pre>
087 * <p>
088 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
089 * </p>
090 * <p>
091 * Violation Message Keys:
092 * </p>
093 * <ul>
094 * <li>
095 * {@code parameter.assignment}
096 * </li>
097 * </ul>
098 *
099 * @since 3.2
100 */
101@FileStatefulCheck
102public final class ParameterAssignmentCheck extends AbstractCheck {
103
104    /**
105     * A key is pointing to the warning message text in "messages.properties"
106     * file.
107     */
108    public static final String MSG_KEY = "parameter.assignment";
109
110    /** Stack of methods' parameters. */
111    private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>();
112    /** Current set of parameters. */
113    private Set<String> parameterNames;
114
115    @Override
116    public int[] getDefaultTokens() {
117        return getRequiredTokens();
118    }
119
120    @Override
121    public int[] getRequiredTokens() {
122        return new int[] {
123            TokenTypes.CTOR_DEF,
124            TokenTypes.METHOD_DEF,
125            TokenTypes.ASSIGN,
126            TokenTypes.PLUS_ASSIGN,
127            TokenTypes.MINUS_ASSIGN,
128            TokenTypes.STAR_ASSIGN,
129            TokenTypes.DIV_ASSIGN,
130            TokenTypes.MOD_ASSIGN,
131            TokenTypes.SR_ASSIGN,
132            TokenTypes.BSR_ASSIGN,
133            TokenTypes.SL_ASSIGN,
134            TokenTypes.BAND_ASSIGN,
135            TokenTypes.BXOR_ASSIGN,
136            TokenTypes.BOR_ASSIGN,
137            TokenTypes.INC,
138            TokenTypes.POST_INC,
139            TokenTypes.DEC,
140            TokenTypes.POST_DEC,
141            TokenTypes.LAMBDA,
142        };
143    }
144
145    @Override
146    public int[] getAcceptableTokens() {
147        return getRequiredTokens();
148    }
149
150    @Override
151    public void beginTree(DetailAST rootAST) {
152        // clear data
153        parameterNamesStack.clear();
154        parameterNames = Collections.emptySet();
155    }
156
157    @Override
158    public void visitToken(DetailAST ast) {
159        final int type = ast.getType();
160        if (TokenUtil.isOfType(type, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)) {
161            visitMethodDef(ast);
162        }
163        else if (type == TokenTypes.LAMBDA) {
164            if (ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
165                visitLambda(ast);
166            }
167        }
168        else {
169            checkNestedIdent(ast);
170        }
171    }
172
173    @Override
174    public void leaveToken(DetailAST ast) {
175        final int type = ast.getType();
176        if (TokenUtil.isOfType(type, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)
177                || type == TokenTypes.LAMBDA
178                && ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
179            parameterNames = parameterNamesStack.pop();
180        }
181    }
182
183    /**
184     * Check if nested ident is parameter.
185     *
186     * @param ast parent of node of ident
187     */
188    private void checkNestedIdent(DetailAST ast) {
189        final DetailAST identAST = ast.getFirstChild();
190
191        if (identAST != null
192            && identAST.getType() == TokenTypes.IDENT
193            && parameterNames.contains(identAST.getText())) {
194            log(ast, MSG_KEY, identAST.getText());
195        }
196    }
197
198    /**
199     * Creates new set of parameters and store old one in stack.
200     *
201     * @param ast a method to process.
202     */
203    private void visitMethodDef(DetailAST ast) {
204        parameterNamesStack.push(parameterNames);
205        parameterNames = new HashSet<>();
206
207        visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS));
208    }
209
210    /**
211     * Creates new set of parameters and store old one in stack.
212     *
213     * @param lambdaAst node of type {@link TokenTypes#LAMBDA}.
214     */
215    private void visitLambda(DetailAST lambdaAst) {
216        parameterNamesStack.push(parameterNames);
217        parameterNames = new HashSet<>();
218
219        DetailAST parameterAst = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
220        if (parameterAst == null) {
221            parameterAst = lambdaAst.getFirstChild();
222        }
223        visitLambdaParameters(parameterAst);
224    }
225
226    /**
227     * Creates new parameter set for given method.
228     *
229     * @param ast a method for process.
230     */
231    private void visitMethodParameters(DetailAST ast) {
232        visitParameters(ast);
233    }
234
235    /**
236     * Creates new parameter set for given lambda expression.
237     *
238     * @param ast a lambda expression parameter to process
239     */
240    private void visitLambdaParameters(DetailAST ast) {
241        if (ast.getType() == TokenTypes.IDENT) {
242            parameterNames.add(ast.getText());
243        }
244        else {
245            visitParameters(ast);
246        }
247    }
248
249    /**
250     * Visits parameter list and adds parameter names to the set.
251     *
252     * @param parametersAst ast node of type {@link TokenTypes#PARAMETERS}.
253     */
254    private void visitParameters(DetailAST parametersAst) {
255        DetailAST parameterDefAST =
256            parametersAst.findFirstToken(TokenTypes.PARAMETER_DEF);
257
258        while (parameterDefAST != null) {
259            if (parameterDefAST.getType() == TokenTypes.PARAMETER_DEF
260                    && !CheckUtil.isReceiverParameter(parameterDefAST)) {
261                final DetailAST param =
262                    parameterDefAST.findFirstToken(TokenTypes.IDENT);
263                parameterNames.add(param.getText());
264            }
265            parameterDefAST = parameterDefAST.getNextSibling();
266        }
267    }
268
269}