001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.Scope;
024import com.puppycrawl.tools.checkstyle.api.TokenTypes;
025
026/**
027 * Contains utility methods for working on scope.
028 *
029 */
030public final class ScopeUtil {
031
032    /** Prevent instantiation. */
033    private ScopeUtil() {
034    }
035
036    /**
037     * Returns the Scope specified by the modifier set.
038     *
039     * @param aMods root node of a modifier set
040     * @return a {@code Scope} value
041     */
042    public static Scope getScopeFromMods(DetailAST aMods) {
043        // default scope
044        Scope returnValue = Scope.PACKAGE;
045        for (DetailAST token = aMods.getFirstChild(); token != null
046                && returnValue == Scope.PACKAGE;
047                token = token.getNextSibling()) {
048            if ("public".equals(token.getText())) {
049                returnValue = Scope.PUBLIC;
050            }
051            else if ("protected".equals(token.getText())) {
052                returnValue = Scope.PROTECTED;
053            }
054            else if ("private".equals(token.getText())) {
055                returnValue = Scope.PRIVATE;
056            }
057        }
058        return returnValue;
059    }
060
061    /**
062     * Returns the scope of the surrounding "block".
063     *
064     * @param node the node to return the scope for
065     * @return the Scope of the surrounding block
066     */
067    public static Scope getSurroundingScope(DetailAST node) {
068        Scope returnValue = null;
069        for (DetailAST token = node.getParent();
070             token != null;
071             token = token.getParent()) {
072            final int type = token.getType();
073            if (TokenUtil.isTypeDeclaration(type)) {
074                final DetailAST mods =
075                    token.findFirstToken(TokenTypes.MODIFIERS);
076                final Scope modScope = getScopeFromMods(mods);
077                if (returnValue == null || returnValue.isIn(modScope)) {
078                    returnValue = modScope;
079                }
080            }
081            else if (type == TokenTypes.LITERAL_NEW) {
082                returnValue = Scope.ANONINNER;
083                // because Scope.ANONINNER is not in any other Scope
084                break;
085            }
086        }
087
088        return returnValue;
089    }
090
091    /**
092     * Returns whether a node is directly contained within a class block.
093     *
094     * @param node the node to check if directly contained within a class block.
095     * @return a {@code boolean} value
096     */
097    public static boolean isInClassBlock(DetailAST node) {
098        return isInBlockOf(node, TokenTypes.CLASS_DEF);
099    }
100
101    /**
102     * Returns whether a node is directly contained within a record block.
103     *
104     * @param node the node to check if directly contained within a record block.
105     * @return a {@code boolean} value
106     */
107    public static boolean isInRecordBlock(DetailAST node) {
108        return isInBlockOf(node, TokenTypes.RECORD_DEF);
109    }
110
111    /**
112     * Returns whether a node is directly contained within an interface block.
113     *
114     * @param node the node to check if directly contained within an interface block.
115     * @return a {@code boolean} value
116     */
117    public static boolean isInInterfaceBlock(DetailAST node) {
118        return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
119    }
120
121    /**
122     * Returns whether a node is directly contained within an annotation block.
123     *
124     * @param node the node to check if directly contained within an annotation block.
125     * @return a {@code boolean} value
126     */
127    public static boolean isInAnnotationBlock(DetailAST node) {
128        return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
129    }
130
131    /**
132     * Returns whether a node is directly contained within a specified block.
133     *
134     * @param node the node to check if directly contained within a specified block.
135     * @param tokenType type of token.
136     * @return a {@code boolean} value
137     */
138    private static boolean isInBlockOf(DetailAST node, int tokenType) {
139        boolean returnValue = false;
140
141        // Loop up looking for a containing interface block
142        for (DetailAST token = node.getParent();
143             token != null && !returnValue;
144             token = token.getParent()) {
145            if (token.getType() == tokenType) {
146                returnValue = true;
147            }
148            else if (token.getType() == TokenTypes.LITERAL_NEW
149                    || TokenUtil.isTypeDeclaration(token.getType())) {
150                break;
151            }
152        }
153
154        return returnValue;
155    }
156
157    /**
158     * Returns whether a node is directly contained within an interface or
159     * annotation block.
160     *
161     * @param node the node to check if directly contained within an interface
162     *     or annotation block.
163     * @return a {@code boolean} value
164     */
165    public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
166        return isInInterfaceBlock(node) || isInAnnotationBlock(node);
167    }
168
169    /**
170     * Returns whether a node is directly contained within an enum block.
171     *
172     * @param node the node to check if directly contained within an enum block.
173     * @return a {@code boolean} value
174     */
175    public static boolean isInEnumBlock(DetailAST node) {
176        boolean returnValue = false;
177
178        // Loop up looking for a containing interface block
179        for (DetailAST token = node.getParent();
180             token != null && !returnValue;
181             token = token.getParent()) {
182            if (token.getType() == TokenTypes.ENUM_DEF) {
183                returnValue = true;
184            }
185            else if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
186                TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
187                TokenTypes.LITERAL_NEW)) {
188                break;
189            }
190        }
191
192        return returnValue;
193    }
194
195    /**
196     * Returns whether the scope of a node is restricted to a code block.
197     * A code block is a method or constructor body, an initializer block, or lambda body.
198     *
199     * @param node the node to check
200     * @return a {@code boolean} value
201     */
202    public static boolean isInCodeBlock(DetailAST node) {
203        boolean returnValue = false;
204        final int[] tokenTypes = {
205            TokenTypes.METHOD_DEF,
206            TokenTypes.CTOR_DEF,
207            TokenTypes.INSTANCE_INIT,
208            TokenTypes.STATIC_INIT,
209            TokenTypes.LAMBDA,
210            TokenTypes.COMPACT_CTOR_DEF,
211        };
212
213        // Loop up looking for a containing code block
214        for (DetailAST token = node.getParent();
215             token != null;
216             token = token.getParent()) {
217            if (TokenUtil.isOfType(token, tokenTypes)) {
218                returnValue = true;
219                break;
220            }
221        }
222
223        return returnValue;
224    }
225
226    /**
227     * Returns whether a node is contained in the outer most type block.
228     *
229     * @param node the node to check
230     * @return a {@code boolean} value
231     */
232    public static boolean isOuterMostType(DetailAST node) {
233        boolean returnValue = true;
234        for (DetailAST parent = node.getParent();
235             parent != null;
236             parent = parent.getParent()) {
237            if (TokenUtil.isTypeDeclaration(parent.getType())) {
238                returnValue = false;
239                break;
240            }
241        }
242
243        return returnValue;
244    }
245
246    /**
247     * Determines whether a node is a local variable definition.
248     * I.e. if it is declared in a code block, a for initializer,
249     * or a catch parameter.
250     *
251     * @param node the node to check.
252     * @return whether aAST is a local variable definition.
253     */
254    public static boolean isLocalVariableDef(DetailAST node) {
255        boolean localVariableDef = false;
256        // variable declaration?
257        if (node.getType() == TokenTypes.VARIABLE_DEF) {
258            final DetailAST parent = node.getParent();
259            localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
260                                TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
261        }
262        // catch parameter?
263        if (node.getType() == TokenTypes.PARAMETER_DEF) {
264            final DetailAST parent = node.getParent();
265            localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
266        }
267
268        if (node.getType() == TokenTypes.RESOURCE) {
269            localVariableDef = node.getChildCount() > 1;
270        }
271        return localVariableDef;
272    }
273
274    /**
275     * Determines whether a node is a class field definition.
276     * I.e. if a variable is not declared in a code block, a for initializer,
277     * or a catch parameter.
278     *
279     * @param node the node to check.
280     * @return whether a node is a class field definition.
281     */
282    public static boolean isClassFieldDef(DetailAST node) {
283        return node.getType() == TokenTypes.VARIABLE_DEF
284                && !isLocalVariableDef(node);
285    }
286
287    /**
288     * Checks whether ast node is in a specific scope.
289     *
290     * @param ast the node to check.
291     * @param scope a {@code Scope} value.
292     * @return true if the ast node is in the scope.
293     */
294    public static boolean isInScope(DetailAST ast, Scope scope) {
295        final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
296        return surroundingScopeOfAstToken == scope;
297    }
298
299}