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