001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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 * Disallows 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 * <p>
047 * To configure the check:
048 * </p>
049 * <pre>
050 * &lt;module name=&quot;ParameterAssignment&quot;/&gt;
051 * </pre>
052 * <p>
053 * Example:
054 * </p>
055 * <pre>
056 * class MyClass {
057 *   int methodOne(int parameter) {
058 *     if (parameter &lt;= 0 ) {
059 *       throw new IllegalArgumentException("A positive value is expected");
060 *     }
061 *     parameter -= 2;  // violation
062 *     return parameter;
063 *   }
064 *
065 *   int methodTwo(int parameter) {
066 *     if (parameter &lt;= 0 ) {
067 *       throw new IllegalArgumentException("A positive value is expected");
068 *     }
069 *     int local = parameter;
070 *     local -= 2;  // OK
071 *     return local;
072 *   }
073 * }
074 * </pre>
075 * <p>
076 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
077 * </p>
078 * <p>
079 * Violation Message Keys:
080 * </p>
081 * <ul>
082 * <li>
083 * {@code parameter.assignment}
084 * </li>
085 * </ul>
086 *
087 * @since 3.2
088 */
089@FileStatefulCheck
090public final class ParameterAssignmentCheck extends AbstractCheck {
091
092    /**
093     * A key is pointing to the warning message text in "messages.properties"
094     * file.
095     */
096    public static final String MSG_KEY = "parameter.assignment";
097
098    /** Stack of methods' parameters. */
099    private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>();
100    /** Current set of parameters. */
101    private Set<String> parameterNames;
102
103    @Override
104    public int[] getDefaultTokens() {
105        return getRequiredTokens();
106    }
107
108    @Override
109    public int[] getRequiredTokens() {
110        return new int[] {
111            TokenTypes.CTOR_DEF,
112            TokenTypes.METHOD_DEF,
113            TokenTypes.ASSIGN,
114            TokenTypes.PLUS_ASSIGN,
115            TokenTypes.MINUS_ASSIGN,
116            TokenTypes.STAR_ASSIGN,
117            TokenTypes.DIV_ASSIGN,
118            TokenTypes.MOD_ASSIGN,
119            TokenTypes.SR_ASSIGN,
120            TokenTypes.BSR_ASSIGN,
121            TokenTypes.SL_ASSIGN,
122            TokenTypes.BAND_ASSIGN,
123            TokenTypes.BXOR_ASSIGN,
124            TokenTypes.BOR_ASSIGN,
125            TokenTypes.INC,
126            TokenTypes.POST_INC,
127            TokenTypes.DEC,
128            TokenTypes.POST_DEC,
129        };
130    }
131
132    @Override
133    public int[] getAcceptableTokens() {
134        return getRequiredTokens();
135    }
136
137    @Override
138    public void beginTree(DetailAST rootAST) {
139        // clear data
140        parameterNamesStack.clear();
141        parameterNames = Collections.emptySet();
142    }
143
144    @Override
145    public void visitToken(DetailAST ast) {
146        switch (ast.getType()) {
147            case TokenTypes.CTOR_DEF:
148            case TokenTypes.METHOD_DEF:
149                visitMethodDef(ast);
150                break;
151            case TokenTypes.ASSIGN:
152            case TokenTypes.PLUS_ASSIGN:
153            case TokenTypes.MINUS_ASSIGN:
154            case TokenTypes.STAR_ASSIGN:
155            case TokenTypes.DIV_ASSIGN:
156            case TokenTypes.MOD_ASSIGN:
157            case TokenTypes.SR_ASSIGN:
158            case TokenTypes.BSR_ASSIGN:
159            case TokenTypes.SL_ASSIGN:
160            case TokenTypes.BAND_ASSIGN:
161            case TokenTypes.BXOR_ASSIGN:
162            case TokenTypes.BOR_ASSIGN:
163                visitAssign(ast);
164                break;
165            case TokenTypes.INC:
166            case TokenTypes.POST_INC:
167            case TokenTypes.DEC:
168            case TokenTypes.POST_DEC:
169                visitIncDec(ast);
170                break;
171            default:
172                throw new IllegalStateException(ast.toString());
173        }
174    }
175
176    @Override
177    public void leaveToken(DetailAST ast) {
178        switch (ast.getType()) {
179            case TokenTypes.CTOR_DEF:
180            case TokenTypes.METHOD_DEF:
181                leaveMethodDef();
182                break;
183            case TokenTypes.ASSIGN:
184            case TokenTypes.PLUS_ASSIGN:
185            case TokenTypes.MINUS_ASSIGN:
186            case TokenTypes.STAR_ASSIGN:
187            case TokenTypes.DIV_ASSIGN:
188            case TokenTypes.MOD_ASSIGN:
189            case TokenTypes.SR_ASSIGN:
190            case TokenTypes.BSR_ASSIGN:
191            case TokenTypes.SL_ASSIGN:
192            case TokenTypes.BAND_ASSIGN:
193            case TokenTypes.BXOR_ASSIGN:
194            case TokenTypes.BOR_ASSIGN:
195            case TokenTypes.INC:
196            case TokenTypes.POST_INC:
197            case TokenTypes.DEC:
198            case TokenTypes.POST_DEC:
199                // Do nothing
200                break;
201            default:
202                throw new IllegalStateException(ast.toString());
203        }
204    }
205
206    /**
207     * Checks if this is assignments of parameter.
208     *
209     * @param ast assignment to check.
210     */
211    private void visitAssign(DetailAST ast) {
212        checkIdent(ast);
213    }
214
215    /**
216     * Checks if this is increment/decrement of parameter.
217     *
218     * @param ast dec/inc to check.
219     */
220    private void visitIncDec(DetailAST ast) {
221        checkIdent(ast);
222    }
223
224    /**
225     * Check if ident is parameter.
226     *
227     * @param ast ident to check.
228     */
229    private void checkIdent(DetailAST ast) {
230        final DetailAST identAST = ast.getFirstChild();
231
232        if (identAST != null
233            && identAST.getType() == TokenTypes.IDENT
234            && parameterNames.contains(identAST.getText())) {
235            log(ast, MSG_KEY, identAST.getText());
236        }
237    }
238
239    /**
240     * Creates new set of parameters and store old one in stack.
241     *
242     * @param ast a method to process.
243     */
244    private void visitMethodDef(DetailAST ast) {
245        parameterNamesStack.push(parameterNames);
246        parameterNames = new HashSet<>();
247
248        visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS));
249    }
250
251    /** Restores old set of parameters. */
252    private void leaveMethodDef() {
253        parameterNames = parameterNamesStack.pop();
254    }
255
256    /**
257     * Creates new parameter set for given method.
258     *
259     * @param ast a method for process.
260     */
261    private void visitMethodParameters(DetailAST ast) {
262        DetailAST parameterDefAST =
263            ast.findFirstToken(TokenTypes.PARAMETER_DEF);
264
265        while (parameterDefAST != null) {
266            if (parameterDefAST.getType() == TokenTypes.PARAMETER_DEF
267                    && !CheckUtil.isReceiverParameter(parameterDefAST)) {
268                final DetailAST param =
269                    parameterDefAST.findFirstToken(TokenTypes.IDENT);
270                parameterNames.add(param.getText());
271            }
272            parameterDefAST = parameterDefAST.getNextSibling();
273        }
274    }
275
276}