001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.ArrayList;
024import java.util.Deque;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Optional;
028import java.util.Set;
029
030import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
031import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
035
036/**
037 * <div>
038 * Checks that references to instance variables and methods of the current object
039 * avoid unnecessary use of {@code this}, unless it is required to resolve
040 * ambiguity with a shadowed field.
041 * </div>
042 *
043 * <p>
044 * By default, a redundant {@code this} is tolerated when the same statement, or the
045 * statement immediately before or after it, contains a {@code this} that is required
046 * to resolve a shadowed field. This keeps groups of assignments consistent, for example
047 * constructor bodies where only some parameters shadow fields. Set
048 * {@code allowAdjacentToRequiredThis} to {@code false} to report every redundant
049 * {@code this}.
050 * </p>
051 *
052 * @since 14.4.0
053 */
054@FileStatefulCheck
055public class RedundantThisCheck extends AbstractCheck {
056
057    /**
058     * A key is pointing to the warning message text in "message.properties" file.
059     */
060    public static final String MSG_KEY_FIELD = "redundant.this.field";
061
062    /**
063     * A key is pointing to the warning message text in "message.properties" file.
064     */
065    public static final String MSG_KEY_METHOD = "redundant.this.method";
066
067    /**
068     * Tracks names (parameters and local variables) that can shadow a field.
069     */
070    private final Deque<Set<String>> scopeStack = new ArrayDeque<>();
071
072    /**
073     * Statements containing a {@code this} that is required to resolve shadowing.
074     */
075    private final Set<DetailAST> requiredStatements = new HashSet<>();
076
077    /**
078     * Redundant {@code this} usages whose reporting is decided in {@code finishTree}.
079     */
080    private final List<RedundantUsage> redundantUsages = new ArrayList<>();
081
082    /**
083     * Control to checking method calls.
084     */
085    private boolean checkMethods;
086
087    /**
088     * Allow a redundant {@code this} when the same or an adjacent statement
089     * contains a required {@code this}.
090     */
091    private boolean allowAdjacentToRequiredThis = true;
092
093    /**
094     * Creates a new {@code RedundantThisCheck} instance.
095     */
096    public RedundantThisCheck() {
097        // no code by default
098    }
099
100    /**
101     * Setter to check whether to check redundant "this" with method call.
102     *
103     * @param checkMethods should we check method call
104     * @since 14.4.0
105     */
106    public void setCheckMethods(boolean checkMethods) {
107        this.checkMethods = checkMethods;
108    }
109
110    /**
111     * Setter to allow redundant "this" when the same statement, or the statement
112     * immediately before or after it, contains a required "this".
113     *
114     * @param allowAdjacentToRequiredThis whether to allow such usages
115     * @since 14.4.0
116     */
117    public void setAllowAdjacentToRequiredThis(boolean allowAdjacentToRequiredThis) {
118        this.allowAdjacentToRequiredThis = allowAdjacentToRequiredThis;
119    }
120
121    @Override
122    public int[] getRequiredTokens() {
123        return getAcceptableTokens();
124    }
125
126    @Override
127    public int[] getDefaultTokens() {
128        return getRequiredTokens();
129    }
130
131    @Override
132    public int[] getAcceptableTokens() {
133        return new int[] {
134            TokenTypes.METHOD_DEF,
135            TokenTypes.CTOR_DEF,
136            TokenTypes.LITERAL_CATCH,
137            TokenTypes.LITERAL_TRY,
138            TokenTypes.LITERAL_THIS,
139            TokenTypes.PATTERN_VARIABLE_DEF,
140            TokenTypes.SLIST,
141            TokenTypes.VARIABLE_DEF,
142        };
143    }
144
145    @Override
146    public void beginTree(DetailAST rootAST) {
147        requiredStatements.clear();
148        redundantUsages.clear();
149    }
150
151    @Override
152    public void visitToken(DetailAST ast) {
153        switch (ast.getType()) {
154            case TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF -> {
155                scopeStack.push(new HashSet<>());
156                addParametersToScope(ast.findFirstToken(TokenTypes.PARAMETERS));
157            }
158            case TokenTypes.LITERAL_CATCH -> {
159                scopeStack.push(new HashSet<>());
160                addParametersToScope(ast);
161            }
162            case TokenTypes.LITERAL_TRY -> {
163                scopeStack.push(new HashSet<>());
164                final Optional<DetailAST> resourcesNode = Optional.of(ast.getFirstChild())
165                    .map(child -> child.findFirstToken(TokenTypes.RESOURCES));
166
167                if (resourcesNode.isPresent()) {
168                    addParametersToScope(resourcesNode.orElseThrow());
169                }
170            }
171            case TokenTypes.SLIST -> scopeStack.push(new HashSet<>());
172            case TokenTypes.VARIABLE_DEF, TokenTypes.PATTERN_VARIABLE_DEF -> {
173                final Set<String> currentScope = scopeStack.peek();
174                if (currentScope != null) {
175                    final String variable = ast.findFirstToken(TokenTypes.IDENT).getText();
176                    currentScope.add(variable);
177                }
178            }
179            default -> {
180                if (ast.getNextSibling() != null) {
181                    checkUnnecessaryThis(ast);
182                }
183            }
184        }
185    }
186
187    @Override
188    public void leaveToken(DetailAST ast) {
189        if (TokenUtil.isOfType(ast,
190                TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF,
191                TokenTypes.LITERAL_CATCH, TokenTypes.LITERAL_TRY,
192                TokenTypes.SLIST)) {
193            scopeStack.pop();
194        }
195    }
196
197    @Override
198    public void finishTree(DetailAST rootAST) {
199        for (RedundantUsage usage : redundantUsages) {
200            if (!allowAdjacentToRequiredThis || !isNearRequiredThis(usage.statement())) {
201                log(usage.literalThis(), usage.messageKey(), usage.name());
202            }
203        }
204    }
205
206    /**
207     * Classifies the use of "this" as required (shadowed) or redundant. Required
208     * usages are recorded by their enclosing statement; redundant usages are
209     * deferred so that {@code finishTree} can decide whether to report them.
210     *
211     * @param literalThis the {@code LITERAL_THIS} token to check
212     */
213    private void checkUnnecessaryThis(DetailAST literalThis) {
214        final DetailAST parent = literalThis.getParent();
215        if (parent.getType() == TokenTypes.DOT) {
216            final DetailAST grandParent = parent.getParent();
217            final String name = literalThis.getNextSibling().getText();
218            final DetailAST statement = findEnclosingStatement(literalThis);
219
220            if (grandParent.getType() == TokenTypes.METHOD_CALL) {
221                if (checkMethods) {
222                    redundantUsages.add(
223                        new RedundantUsage(literalThis, statement, MSG_KEY_METHOD, name));
224                }
225            }
226            else if (isShadowedByLocalName(name)) {
227                if (statement != null) {
228                    requiredStatements.add(statement);
229                }
230            }
231            else {
232                redundantUsages.add(
233                    new RedundantUsage(literalThis, statement, MSG_KEY_FIELD, name));
234            }
235        }
236    }
237
238    /**
239     * Finds the statement (a direct child of an {@code SLIST}) enclosing the given node.
240     *
241     * @param node the node
242     * @return the statement, or {@code null} if the node is not inside a block
243     *     (for example, a field initializer)
244     */
245    private static DetailAST findEnclosingStatement(DetailAST node) {
246        DetailAST statement = node;
247        while (!TokenUtil.isOfType(statement.getParent(),
248                    TokenTypes.SLIST, TokenTypes.OBJBLOCK)) {
249            statement = statement.getParent();
250        }
251
252        DetailAST result = null;
253        if (statement.getParent().getType() == TokenTypes.SLIST) {
254            result = statement;
255        }
256
257        return result;
258    }
259
260    /**
261     * Checks whether the given statement, or the statement immediately before or
262     * after it, contains a required {@code this}.
263     *
264     * @param statement the statement to check, may be {@code null}
265     * @return {@code true} if a required {@code this} is in the same or an adjacent statement
266     */
267    private boolean isNearRequiredThis(DetailAST statement) {
268        return statement != null
269                && (requiredStatements.contains(statement)
270                    || requiredStatements.contains(previousStatement(statement))
271                    || requiredStatements.contains(nextStatement(statement)));
272    }
273
274    /**
275     * Gets the statement before the given one, stepping over the {@code SEMI}
276     * that terminates the previous statement.
277     *
278     * @param statement the statement
279     * @return the previous statement, or {@code null} if there is none
280     */
281    private static DetailAST previousStatement(DetailAST statement) {
282        DetailAST result = statement.getPreviousSibling();
283        if (result != null && result.getType() == TokenTypes.SEMI) {
284            result = result.getPreviousSibling();
285        }
286        return result;
287    }
288
289    /**
290     * Gets the statement after the given one, stepping over the {@code SEMI}
291     * that terminates the given statement.
292     *
293     * @param statement the statement
294     * @return the next statement, or {@code null} if there is none
295     */
296    private static DetailAST nextStatement(DetailAST statement) {
297        DetailAST result = statement.getNextSibling();
298        if (result != null && result.getType() == TokenTypes.SEMI) {
299            result = result.getNextSibling();
300        }
301        return result;
302    }
303
304    /**
305     * Checks whether the given variable name is shadowed by any parameter or
306     * local variable currently in scope, meaning that {@code this.name} is
307     * necessary to distinguish the field from the shadowing name.
308     *
309     * @param name the variable name following "this"
310     * @return {@code true} if a shadowing name exists in any enclosing scope
311     */
312    private boolean isShadowedByLocalName(String name) {
313        boolean result = false;
314        for (Set<String> scope : scopeStack) {
315            if (scope.contains(name)) {
316                result = true;
317                break;
318            }
319        }
320        return result;
321    }
322
323    /**
324     * Adds all parameters found within {@code parametersNode} to the current
325     * (top-of-stack) scope. {@code parametersNode} may be either a
326     * {@code PARAMETERS} node (for methods and constructors) or a
327     * {@code LITERAL_CATCH} node (for catch clauses); both carry their
328     * parameter definitions as direct {@code PARAMETER_DEF} children.
329     *
330     * @param parametersNode the node whose {@code PARAMETER_DEF} children
331     *     should be added to the current scope
332     */
333    private void addParametersToScope(DetailAST parametersNode) {
334        DetailAST child = parametersNode.getFirstChild();
335        while (child != null) {
336            final DetailAST ident = child.findFirstToken(TokenTypes.IDENT);
337            if (ident != null) {
338                scopeStack.peek().add(ident.getText());
339            }
340            child = child.getNextSibling();
341        }
342    }
343
344    /**
345     * A redundant {@code this} usage waiting for a reporting decision.
346     *
347     * @param literalThis the {@code LITERAL_THIS} token
348     * @param statement the enclosing statement, or {@code null} if outside a block
349     * @param messageKey the violation message key
350     * @param name the member name following "this"
351     */
352    private record RedundantUsage(
353            DetailAST literalThis, DetailAST statement, String messageKey, String name) {
354    }
355
356}