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.utils;
021
022import java.util.Optional;
023
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.Scope;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 * Contains utility methods for working on scope.
030 *
031 */
032public final class ScopeUtil {
033
034    /** Prevent instantiation. */
035    private ScopeUtil() {
036    }
037
038    /**
039     * Returns the {@code Scope} explicitly specified by the modifier set.
040     * Returns {@code null} if there are no modifiers.
041     *
042     * @param aMods root node of a modifier set
043     * @return a {@code Scope} value or {@code null}
044     */
045    public static Scope getDeclaredScopeFromMods(DetailAST aMods) {
046        Scope result = null;
047        for (DetailAST token = aMods.getFirstChild(); token != null;
048             token = token.getNextSibling()) {
049            result = switch (token.getType()) {
050                case TokenTypes.LITERAL_PUBLIC -> Scope.PUBLIC;
051                case TokenTypes.LITERAL_PROTECTED -> Scope.PROTECTED;
052                case TokenTypes.LITERAL_PRIVATE -> Scope.PRIVATE;
053                default -> result;
054            };
055        }
056        return result;
057    }
058
059    /**
060     * Returns the {@code Scope} for a given {@code DetailAST}.
061     *
062     * @param ast the DetailAST to examine
063     * @return a {@code Scope} value
064     */
065    public static Scope getScope(DetailAST ast) {
066        return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
067                .map(ScopeUtil::getDeclaredScopeFromMods)
068                .orElseGet(() -> getDefaultScope(ast));
069    }
070
071    /**
072     * Returns the {@code Scope} specified by the modifier set. If no modifiers are present,
073     * the default scope is used.
074     *
075     * @param aMods root node of a modifier set
076     * @return a {@code Scope} value
077     * @see #getDefaultScope(DetailAST)
078     */
079    public static Scope getScopeFromMods(DetailAST aMods) {
080        return Optional.ofNullable(getDeclaredScopeFromMods(aMods))
081                .orElseGet(() -> getDefaultScope(aMods));
082    }
083
084    /**
085     * Returns the default {@code Scope} for a {@code DetailAST}.
086     *
087     * <p>The following rules are taken into account:</p>
088     * <ul>
089     *     <li>enum constants are public</li>
090     *     <li>enum constructors are private</li>
091     *     <li>interface members are public</li>
092     *     <li>everything else is package private</li>
093     * </ul>
094     *
095     * @param ast DetailAST to process
096     * @return a {@code Scope} value
097     */
098    private static Scope getDefaultScope(DetailAST ast) {
099        final Scope result;
100        if (isInEnumBlock(ast)) {
101            if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
102                result = Scope.PUBLIC;
103            }
104            else if (ast.getType() == TokenTypes.CTOR_DEF) {
105                result = Scope.PRIVATE;
106            }
107            else {
108                result = Scope.PACKAGE;
109            }
110        }
111        else if (isInInterfaceOrAnnotationBlock(ast)) {
112            result = Scope.PUBLIC;
113        }
114        else {
115            result = Scope.PACKAGE;
116        }
117        return result;
118    }
119
120    /**
121     * Returns the scope of the surrounding "block".
122     *
123     * @param node the node to return the scope for
124     * @return the Scope of the surrounding block
125     */
126    public static Scope getSurroundingScope(DetailAST node) {
127        Scope returnValue = null;
128        for (DetailAST token = node;
129             token != null;
130             token = token.getParent()) {
131            final int type = token.getType();
132            if (TokenUtil.isTypeDeclaration(type)) {
133                final Scope tokenScope = getScope(token);
134                if (returnValue == null || returnValue.isIn(tokenScope)) {
135                    returnValue = tokenScope;
136                }
137            }
138            else if (type == TokenTypes.LITERAL_NEW) {
139                returnValue = Scope.ANONINNER;
140                // because Scope.ANONINNER is not in any other Scope
141                break;
142            }
143        }
144
145        return returnValue;
146    }
147
148    /**
149     * Returns whether a node is directly contained within a class block.
150     *
151     * @param node the node to check if directly contained within a class block.
152     * @return a {@code boolean} value
153     */
154    public static boolean isInClassBlock(DetailAST node) {
155        return isInBlockOf(node, TokenTypes.CLASS_DEF);
156    }
157
158    /**
159     * Returns whether a node is directly contained within a record block.
160     *
161     * @param node the node to check if directly contained within a record block.
162     * @return a {@code boolean} value
163     */
164    public static boolean isInRecordBlock(DetailAST node) {
165        return isInBlockOf(node, TokenTypes.RECORD_DEF);
166    }
167
168    /**
169     * Returns whether a node is directly contained within an interface block.
170     *
171     * @param node the node to check if directly contained within an interface block.
172     * @return a {@code boolean} value
173     */
174    public static boolean isInInterfaceBlock(DetailAST node) {
175        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
176    }
177
178    /**
179     * Returns whether a node is directly contained within an annotation block.
180     *
181     * @param node the node to check if directly contained within an annotation block.
182     * @return a {@code boolean} value
183     */
184    public static boolean isInAnnotationBlock(DetailAST node) {
185        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
186    }
187
188    /**
189     * Returns whether a node is directly contained within a specified block.
190     *
191     * @param node the node to check if directly contained within a specified block.
192     * @param tokenType type of token.
193     * @return a {@code boolean} value
194     */
195    public static boolean isInBlockOf(DetailAST node, int tokenType) {
196        boolean returnValue = false;
197
198        // Loop up looking for a containing interface block
199        for (DetailAST token = node.getParent();
200             token != null; token = token.getParent()) {
201            if (TokenUtil.isOfType(token, TokenTypes.LITERAL_NEW, tokenType)
202                    || TokenUtil.isTypeDeclaration(token.getType())) {
203                returnValue = token.getType() == tokenType;
204                break;
205            }
206        }
207        return returnValue;
208    }
209
210    /**
211     * Returns whether a node is directly contained within an interface or
212     * annotation block.
213     *
214     * @param node the node to check if directly contained within an interface
215     *     or annotation block.
216     * @return a {@code boolean} value
217     */
218    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
219        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
220    }
221
222    /**
223     * Returns whether a node is directly contained within an enum block.
224     *
225     * @param node the node to check if directly contained within an enum block.
226     * @return a {@code boolean} value
227     */
228    public static boolean isInEnumBlock(DetailAST node) {
229        boolean returnValue = false;
230
231        // Loop up looking for a containing interface block
232        for (DetailAST token = node.getParent();
233             token != null; token = token.getParent()) {
234            if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
235                TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
236                TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) {
237                returnValue = token.getType() == TokenTypes.ENUM_DEF;
238                break;
239            }
240        }
241
242        return returnValue;
243    }
244
245    /**
246     * Returns whether the scope of a node is restricted to a code block.
247     * A code block is a method or constructor body, an initializer block, or lambda body.
248     *
249     * @param node the node to check
250     * @return a {@code boolean} value
251     */
252    public static boolean isInCodeBlock(DetailAST node) {
253        boolean returnValue = false;
254        final int[] tokenTypes = {
255            TokenTypes.METHOD_DEF,
256            TokenTypes.CTOR_DEF,
257            TokenTypes.INSTANCE_INIT,
258            TokenTypes.STATIC_INIT,
259            TokenTypes.LAMBDA,
260            TokenTypes.COMPACT_CTOR_DEF,
261        };
262
263        // Loop up looking for a containing code block
264        for (DetailAST token = node.getParent();
265             token != null;
266             token = token.getParent()) {
267            if (TokenUtil.isOfType(token, tokenTypes)) {
268                returnValue = true;
269                break;
270            }
271        }
272
273        return returnValue;
274    }
275
276    /**
277     * Returns whether a node is contained in the outermost type block.
278     *
279     * @param node the node to check
280     * @return a {@code boolean} value
281     */
282    public static boolean isOuterMostType(DetailAST node) {
283        boolean returnValue = true;
284        for (DetailAST parent = node.getParent();
285             parent != null;
286             parent = parent.getParent()) {
287            if (TokenUtil.isTypeDeclaration(parent.getType())) {
288                returnValue = false;
289                break;
290            }
291        }
292
293        return returnValue;
294    }
295
296    /**
297     * Determines whether a node is a local variable definition.
298     * I.e. if it is declared in a code block, a for initializer,
299     * or a catch parameter.
300     *
301     * @param node the node to check.
302     * @return whether aAST is a local variable definition.
303     */
304    public static boolean isLocalVariableDef(DetailAST node) {
305        final boolean localVariableDef;
306        // variable declaration?
307        if (node.getType() == TokenTypes.VARIABLE_DEF) {
308            final DetailAST parent = node.getParent();
309            localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
310                                TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
311        }
312
313        else if (node.getType() == TokenTypes.RESOURCE) {
314            localVariableDef = node.getChildCount() > 1;
315        }
316
317        // catch parameter?
318        else if (node.getType() == TokenTypes.PARAMETER_DEF) {
319            final DetailAST parent = node.getParent();
320            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
321        }
322
323        else {
324            localVariableDef = false;
325        }
326
327        return localVariableDef;
328    }
329
330    /**
331     * Determines whether a node is a class field definition.
332     * I.e. if a variable is not declared in a code block, a for initializer,
333     * or a catch parameter.
334     *
335     * @param node the node to check.
336     * @return whether a node is a class field definition.
337     */
338    public static boolean isClassFieldDef(DetailAST node) {
339        return node.getType() == TokenTypes.VARIABLE_DEF
340                && !isLocalVariableDef(node);
341    }
342
343    /**
344     * Checks whether ast node is in a specific scope.
345     *
346     * @param ast the node to check.
347     * @param scope a {@code Scope} value.
348     * @return true if the ast node is in the scope.
349     */
350    public static boolean isInScope(DetailAST ast, Scope scope) {
351        final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
352        return surroundingScopeOfAstToken == scope;
353    }
354
355}