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