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