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.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 mods root node of a modifier set
043     * @return a {@code Scope} value or {@code null}
044     */
045    public static Optional<Scope> getDeclaredScopeFromMods(DetailAST mods) {
046        Optional<Scope> result = Optional.empty();
047        for (DetailAST token = mods.getFirstChild(); token != null;
048             token = token.getNextSibling()) {
049            result = switch (token.getType()) {
050                case TokenTypes.LITERAL_PUBLIC -> Optional.of(Scope.PUBLIC);
051                case TokenTypes.LITERAL_PROTECTED -> Optional.of(Scope.PROTECTED);
052                case TokenTypes.LITERAL_PRIVATE -> Optional.of(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                .flatMap(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 mods root node of a modifier set
076     * @return a {@code Scope} value
077     * @see #getDefaultScope(DetailAST)
078     */
079    public static Scope getScopeFromMods(DetailAST mods) {
080        return getDeclaredScopeFromMods(mods)
081                .orElseGet(() -> getDefaultScope(mods));
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 Optional<Scope> getSurroundingScope(DetailAST node) {
127        Optional<Scope> returnValue = Optional.empty();
128        DetailAST child = null;
129        for (DetailAST token = node;
130             token != null;
131             child = token, token = token.getParent()) {
132            final int type = token.getType();
133            if (TokenUtil.isTypeDeclaration(type)) {
134                final Scope tokenScope = getScope(token);
135                if (returnValue.isEmpty() || returnValue.get().isIn(tokenScope)) {
136                    returnValue = Optional.of(tokenScope);
137                }
138            }
139            else if (type == TokenTypes.LITERAL_NEW) {
140                returnValue = Optional.of(Scope.ANONINNER);
141                // because Scope.ANONINNER is not in any other Scope
142                break;
143            }
144            else if (type == TokenTypes.COMPACT_COMPILATION_UNIT
145                        && !TokenUtil.isOfType(child, TokenTypes.IMPORT,
146                                TokenTypes.STATIC_IMPORT, TokenTypes.MODULE_IMPORT)
147                        && (returnValue.isEmpty() || returnValue.get().isIn(Scope.PACKAGE))) {
148                returnValue = Optional.of(Scope.PACKAGE);
149            }
150        }
151
152        return returnValue;
153    }
154
155    /**
156     * Returns whether a node is directly contained within a class block.
157     *
158     * @param node the node to check if directly contained within a class block.
159     * @return a {@code boolean} value
160     */
161    public static boolean isInClassBlock(DetailAST node) {
162        return isInBlockOf(node, TokenTypes.CLASS_DEF);
163    }
164
165    /**
166     * Returns whether a node is directly contained within a record block.
167     *
168     * @param node the node to check if directly contained within a record block.
169     * @return a {@code boolean} value
170     */
171    public static boolean isInRecordBlock(DetailAST node) {
172        return isInBlockOf(node, TokenTypes.RECORD_DEF);
173    }
174
175    /**
176     * Returns whether a node is directly contained within an interface block.
177     *
178     * @param node the node to check if directly contained within an interface block.
179     * @return a {@code boolean} value
180     */
181    public static boolean isInInterfaceBlock(DetailAST node) {
182        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
183    }
184
185    /**
186     * Returns whether a node is directly contained within an annotation block.
187     *
188     * @param node the node to check if directly contained within an annotation block.
189     * @return a {@code boolean} value
190     */
191    public static boolean isInAnnotationBlock(DetailAST node) {
192        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
193    }
194
195    /**
196     * Returns whether a node is directly contained within a specified block.
197     *
198     * @param node the node to check if directly contained within a specified block.
199     * @param tokenType type of token.
200     * @return a {@code boolean} value
201     */
202    public static boolean isInBlockOf(DetailAST node, int tokenType) {
203        boolean returnValue = false;
204
205        // Loop up looking for a containing interface block
206        for (DetailAST token = node.getParent();
207             token != null; token = token.getParent()) {
208            if (TokenUtil.isOfType(token, TokenTypes.LITERAL_NEW, tokenType)
209                    || TokenUtil.isTypeDeclaration(token.getType())) {
210                returnValue = token.getType() == tokenType;
211                break;
212            }
213        }
214        return returnValue;
215    }
216
217    /**
218     * Returns whether a node is directly contained within an interface or annotation block.
219     *
220     * @param node the node to check if directly contained within an interface or annotation block.
221     * @return a {@code boolean} value
222     */
223    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
224        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
225    }
226
227    /**
228     * Returns whether a node is directly contained within an enum block.
229     *
230     * @param node the node to check if directly contained within an enum block.
231     * @return a {@code boolean} value
232     */
233    public static boolean isInEnumBlock(DetailAST node) {
234        boolean returnValue = false;
235
236        // Loop up looking for a containing interface block
237        for (DetailAST token = node.getParent();
238             token != null; token = token.getParent()) {
239            if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
240                TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
241                TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) {
242                returnValue = token.getType() == TokenTypes.ENUM_DEF;
243                break;
244            }
245        }
246
247        return returnValue;
248    }
249
250    /**
251     * Returns whether the scope of a node is restricted to a code block.
252     * A code block is a method or constructor body, an initializer block, or lambda body.
253     *
254     * @param node the node to check
255     * @return a {@code boolean} value
256     */
257    public static boolean isInCodeBlock(DetailAST node) {
258        boolean returnValue = false;
259        final int[] tokenTypes = {
260            TokenTypes.METHOD_DEF,
261            TokenTypes.CTOR_DEF,
262            TokenTypes.INSTANCE_INIT,
263            TokenTypes.STATIC_INIT,
264            TokenTypes.LAMBDA,
265            TokenTypes.COMPACT_CTOR_DEF,
266        };
267
268        // Loop up looking for a containing code block
269        for (DetailAST token = node.getParent();
270             token != null;
271             token = token.getParent()) {
272            if (TokenUtil.isOfType(token, tokenTypes)) {
273                returnValue = true;
274                break;
275            }
276        }
277
278        return returnValue;
279    }
280
281    /**
282     * Returns whether a node is contained in the outermost type block.
283     *
284     * @param node the node to check
285     * @return a {@code boolean} value
286     */
287    public static boolean isOuterMostType(DetailAST node) {
288        boolean returnValue = true;
289        for (DetailAST parent = node.getParent();
290             parent != null;
291             parent = parent.getParent()) {
292            if (TokenUtil.isTypeDeclaration(parent.getType())
293                    || parent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
294                returnValue = false;
295                break;
296            }
297        }
298
299        return returnValue;
300    }
301
302    /**
303     * Determines whether a node is a local variable definition.
304     * I.e. if it is declared in a code block, a for initializer,
305     * or a catch parameter.
306     *
307     * @param node the node to check.
308     * @return whether aAST is a local variable definition.
309     */
310    public static boolean isLocalVariableDef(DetailAST node) {
311        final boolean localVariableDef;
312        // variable declaration?
313        if (node.getType() == TokenTypes.VARIABLE_DEF) {
314            final DetailAST parent = node.getParent();
315            localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
316                                TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
317        }
318
319        else if (node.getType() == TokenTypes.RESOURCE) {
320            localVariableDef = node.getChildCount() > 1;
321        }
322
323        // catch parameter?
324        else if (node.getType() == TokenTypes.PARAMETER_DEF) {
325            final DetailAST parent = node.getParent();
326            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
327        }
328
329        else {
330            localVariableDef = false;
331        }
332
333        return localVariableDef;
334    }
335
336    /**
337     * Determines whether a node is a class field definition.
338     * I.e. if a variable is not declared in a code block, a for initializer,
339     * or a catch parameter.
340     *
341     * @param node the node to check.
342     * @return whether a node is a class field definition.
343     */
344    public static boolean isClassFieldDef(DetailAST node) {
345        return node.getType() == TokenTypes.VARIABLE_DEF
346                && !isLocalVariableDef(node);
347    }
348
349    /**
350     * Checks whether ast node is in a specific scope.
351     *
352     * @param ast the node to check.
353     * @param scope a {@code Scope} value.
354     * @return true if the ast node is in the scope.
355     */
356    public static boolean isInScope(DetailAST ast, Scope scope) {
357        return getSurroundingScope(ast)
358                .map(surroundingScope -> {
359                    return surroundingScope == scope;
360                })
361                .orElse(Boolean.FALSE);
362    }
363
364}