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