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