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