001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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;
033import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
034
035/**
036 * <div>
037 * Disallows assignment of parameters.
038 * </div>
039 *
040 * <p>
041 * Rationale:
042 * Parameter assignment is often considered poor
043 * programming practice. Forcing developers to declare
044 * parameters as final is often onerous. Having a check
045 * ensure that parameters are never assigned would give
046 * the best of both worlds.
047 * </p>
048 *
049 * <p>
050 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
051 * </p>
052 *
053 * <p>
054 * Violation Message Keys:
055 * </p>
056 * <ul>
057 * <li>
058 * {@code parameter.assignment}
059 * </li>
060 * </ul>
061 *
062 * @since 3.2
063 */
064@FileStatefulCheck
065public final class ParameterAssignmentCheck extends AbstractCheck {
066
067    /**
068     * A key is pointing to the warning message text in "messages.properties"
069     * file.
070     */
071    public static final String MSG_KEY = "parameter.assignment";
072
073    /** Stack of methods' parameters. */
074    private final Deque<Set<String>> parameterNamesStack = new ArrayDeque<>();
075    /** Current set of parameters. */
076    private Set<String> parameterNames;
077
078    @Override
079    public int[] getDefaultTokens() {
080        return getRequiredTokens();
081    }
082
083    @Override
084    public int[] getRequiredTokens() {
085        return new int[] {
086            TokenTypes.CTOR_DEF,
087            TokenTypes.METHOD_DEF,
088            TokenTypes.ASSIGN,
089            TokenTypes.PLUS_ASSIGN,
090            TokenTypes.MINUS_ASSIGN,
091            TokenTypes.STAR_ASSIGN,
092            TokenTypes.DIV_ASSIGN,
093            TokenTypes.MOD_ASSIGN,
094            TokenTypes.SR_ASSIGN,
095            TokenTypes.BSR_ASSIGN,
096            TokenTypes.SL_ASSIGN,
097            TokenTypes.BAND_ASSIGN,
098            TokenTypes.BXOR_ASSIGN,
099            TokenTypes.BOR_ASSIGN,
100            TokenTypes.INC,
101            TokenTypes.POST_INC,
102            TokenTypes.DEC,
103            TokenTypes.POST_DEC,
104            TokenTypes.LAMBDA,
105        };
106    }
107
108    @Override
109    public int[] getAcceptableTokens() {
110        return getRequiredTokens();
111    }
112
113    @Override
114    public void beginTree(DetailAST rootAST) {
115        // clear data
116        parameterNamesStack.clear();
117        parameterNames = Collections.emptySet();
118    }
119
120    @Override
121    public void visitToken(DetailAST ast) {
122        final int type = ast.getType();
123        if (TokenUtil.isOfType(type, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)) {
124            visitMethodDef(ast);
125        }
126        else if (type == TokenTypes.LAMBDA) {
127            if (ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
128                visitLambda(ast);
129            }
130        }
131        else {
132            checkNestedIdent(ast);
133        }
134    }
135
136    @Override
137    public void leaveToken(DetailAST ast) {
138        final int type = ast.getType();
139        if (TokenUtil.isOfType(type, TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF)
140                || type == TokenTypes.LAMBDA
141                && ast.getParent().getType() != TokenTypes.SWITCH_RULE) {
142            parameterNames = parameterNamesStack.pop();
143        }
144    }
145
146    /**
147     * Check if nested ident is parameter.
148     *
149     * @param ast parent of node of ident
150     */
151    private void checkNestedIdent(DetailAST ast) {
152        final DetailAST identAST = ast.getFirstChild();
153
154        if (identAST != null
155            && identAST.getType() == TokenTypes.IDENT
156            && parameterNames.contains(identAST.getText())) {
157            log(ast, MSG_KEY, identAST.getText());
158        }
159    }
160
161    /**
162     * Creates new set of parameters and store old one in stack.
163     *
164     * @param ast a method to process.
165     */
166    private void visitMethodDef(DetailAST ast) {
167        parameterNamesStack.push(parameterNames);
168        parameterNames = new HashSet<>();
169
170        visitMethodParameters(ast.findFirstToken(TokenTypes.PARAMETERS));
171    }
172
173    /**
174     * Creates new set of parameters and store old one in stack.
175     *
176     * @param lambdaAst node of type {@link TokenTypes#LAMBDA}.
177     */
178    private void visitLambda(DetailAST lambdaAst) {
179        parameterNamesStack.push(parameterNames);
180        parameterNames = new HashSet<>();
181
182        DetailAST parameterAst = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
183        if (parameterAst == null) {
184            parameterAst = lambdaAst.getFirstChild();
185        }
186        visitLambdaParameters(parameterAst);
187    }
188
189    /**
190     * Creates new parameter set for given method.
191     *
192     * @param ast a method for process.
193     */
194    private void visitMethodParameters(DetailAST ast) {
195        visitParameters(ast);
196    }
197
198    /**
199     * Creates new parameter set for given lambda expression.
200     *
201     * @param ast a lambda expression parameter to process
202     */
203    private void visitLambdaParameters(DetailAST ast) {
204        if (ast.getType() == TokenTypes.IDENT) {
205            parameterNames.add(ast.getText());
206        }
207        else {
208            visitParameters(ast);
209        }
210    }
211
212    /**
213     * Visits parameter list and adds parameter names to the set.
214     *
215     * @param parametersAst ast node of type {@link TokenTypes#PARAMETERS}.
216     */
217    private void visitParameters(DetailAST parametersAst) {
218        DetailAST parameterDefAST =
219            parametersAst.findFirstToken(TokenTypes.PARAMETER_DEF);
220
221        while (parameterDefAST != null) {
222            if (!CheckUtil.isReceiverParameter(parameterDefAST)) {
223                final DetailAST param =
224                    parameterDefAST.findFirstToken(TokenTypes.IDENT);
225                parameterNames.add(param.getText());
226            }
227            parameterDefAST = parameterDefAST.getNextSibling();
228        }
229    }
230
231}