View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.utils;
21  
22  import java.util.Optional;
23  
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.api.Scope;
26  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
27  
28  /**
29   * Contains utility methods for working on scope.
30   *
31   */
32  public final class ScopeUtil {
33  
34      /** Prevent instantiation. */
35      private ScopeUtil() {
36      }
37  
38      /**
39       * Returns the {@code Scope} explicitly specified by the modifier set.
40       * Returns {@code null} if there are no modifiers.
41       *
42       * @param aMods root node of a modifier set
43       * @return a {@code Scope} value or {@code null}
44       */
45      public static Scope getDeclaredScopeFromMods(DetailAST aMods) {
46          Scope result = null;
47          for (DetailAST token = aMods.getFirstChild(); token != null;
48               token = token.getNextSibling()) {
49              switch (token.getType()) {
50                  case TokenTypes.LITERAL_PUBLIC:
51                      result = Scope.PUBLIC;
52                      break;
53                  case TokenTypes.LITERAL_PROTECTED:
54                      result = Scope.PROTECTED;
55                      break;
56                  case TokenTypes.LITERAL_PRIVATE:
57                      result = Scope.PRIVATE;
58                      break;
59                  default:
60                      break;
61              }
62          }
63          return result;
64      }
65  
66      /**
67       * Returns the {@code Scope} for a given {@code DetailAST}.
68       *
69       * @param ast the DetailAST to examine
70       * @return a {@code Scope} value
71       */
72      public static Scope getScope(DetailAST ast) {
73          return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
74                  .map(ScopeUtil::getDeclaredScopeFromMods)
75                  .orElseGet(() -> getDefaultScope(ast));
76      }
77  
78      /**
79       * Returns the {@code Scope} specified by the modifier set. If no modifiers are present,
80       * the default scope is used.
81       *
82       * @param aMods root node of a modifier set
83       * @return a {@code Scope} value
84       * @see #getDefaultScope(DetailAST)
85       */
86      public static Scope getScopeFromMods(DetailAST aMods) {
87          return Optional.ofNullable(getDeclaredScopeFromMods(aMods))
88                  .orElseGet(() -> getDefaultScope(aMods));
89      }
90  
91      /**
92       * Returns the default {@code Scope} for a {@code DetailAST}.
93       *
94       * <p>The following rules are taken into account:</p>
95       * <ul>
96       *     <li>enum constants are public</li>
97       *     <li>enum constructors are private</li>
98       *     <li>interface members are public</li>
99       *     <li>everything else is package private</li>
100      * </ul>
101      *
102      * @param ast DetailAST to process
103      * @return a {@code Scope} value
104      */
105     private static Scope getDefaultScope(DetailAST ast) {
106         final Scope result;
107         if (isInEnumBlock(ast)) {
108             if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
109                 result = Scope.PUBLIC;
110             }
111             else if (ast.getType() == TokenTypes.CTOR_DEF) {
112                 result = Scope.PRIVATE;
113             }
114             else {
115                 result = Scope.PACKAGE;
116             }
117         }
118         else if (isInInterfaceOrAnnotationBlock(ast)) {
119             result = Scope.PUBLIC;
120         }
121         else {
122             result = Scope.PACKAGE;
123         }
124         return result;
125     }
126 
127     /**
128      * Returns the scope of the surrounding "block".
129      *
130      * @param node the node to return the scope for
131      * @return the Scope of the surrounding block
132      */
133     public static Scope getSurroundingScope(DetailAST node) {
134         Scope returnValue = null;
135         for (DetailAST token = node;
136              token != null;
137              token = token.getParent()) {
138             final int type = token.getType();
139             if (TokenUtil.isTypeDeclaration(type)) {
140                 final Scope tokenScope = getScope(token);
141                 if (returnValue == null || returnValue.isIn(tokenScope)) {
142                     returnValue = tokenScope;
143                 }
144             }
145             else if (type == TokenTypes.LITERAL_NEW) {
146                 returnValue = Scope.ANONINNER;
147                 // because Scope.ANONINNER is not in any other Scope
148                 break;
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
219      * annotation block.
220      *
221      * @param node the node to check if directly contained within an interface
222      *     or annotation block.
223      * @return a {@code boolean} value
224      */
225     public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
226         return isInInterfaceBlock(node) || isInAnnotationBlock(node);
227     }
228 
229     /**
230      * Returns whether a node is directly contained within an enum block.
231      *
232      * @param node the node to check if directly contained within an enum block.
233      * @return a {@code boolean} value
234      */
235     public static boolean isInEnumBlock(DetailAST node) {
236         boolean returnValue = false;
237 
238         // Loop up looking for a containing interface block
239         for (DetailAST token = node.getParent();
240              token != null; token = token.getParent()) {
241             if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
242                 TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
243                 TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) {
244                 returnValue = token.getType() == TokenTypes.ENUM_DEF;
245                 break;
246             }
247         }
248 
249         return returnValue;
250     }
251 
252     /**
253      * Returns whether the scope of a node is restricted to a code block.
254      * A code block is a method or constructor body, an initializer block, or lambda body.
255      *
256      * @param node the node to check
257      * @return a {@code boolean} value
258      */
259     public static boolean isInCodeBlock(DetailAST node) {
260         boolean returnValue = false;
261         final int[] tokenTypes = {
262             TokenTypes.METHOD_DEF,
263             TokenTypes.CTOR_DEF,
264             TokenTypes.INSTANCE_INIT,
265             TokenTypes.STATIC_INIT,
266             TokenTypes.LAMBDA,
267             TokenTypes.COMPACT_CTOR_DEF,
268         };
269 
270         // Loop up looking for a containing code block
271         for (DetailAST token = node.getParent();
272              token != null;
273              token = token.getParent()) {
274             if (TokenUtil.isOfType(token, tokenTypes)) {
275                 returnValue = true;
276                 break;
277             }
278         }
279 
280         return returnValue;
281     }
282 
283     /**
284      * Returns whether a node is contained in the outermost type block.
285      *
286      * @param node the node to check
287      * @return a {@code boolean} value
288      */
289     public static boolean isOuterMostType(DetailAST node) {
290         boolean returnValue = true;
291         for (DetailAST parent = node.getParent();
292              parent != null;
293              parent = parent.getParent()) {
294             if (TokenUtil.isTypeDeclaration(parent.getType())) {
295                 returnValue = false;
296                 break;
297             }
298         }
299 
300         return returnValue;
301     }
302 
303     /**
304      * Determines whether a node is a local variable definition.
305      * I.e. if it is declared in a code block, a for initializer,
306      * or a catch parameter.
307      *
308      * @param node the node to check.
309      * @return whether aAST is a local variable definition.
310      */
311     public static boolean isLocalVariableDef(DetailAST node) {
312         final boolean localVariableDef;
313         // variable declaration?
314         if (node.getType() == TokenTypes.VARIABLE_DEF) {
315             final DetailAST parent = node.getParent();
316             localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
317                                 TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
318         }
319 
320         else if (node.getType() == TokenTypes.RESOURCE) {
321             localVariableDef = node.getChildCount() > 1;
322         }
323 
324         // catch parameter?
325         else if (node.getType() == TokenTypes.PARAMETER_DEF) {
326             final DetailAST parent = node.getParent();
327             localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
328         }
329 
330         else {
331             localVariableDef = false;
332         }
333 
334         return localVariableDef;
335     }
336 
337     /**
338      * Determines whether a node is a class field definition.
339      * I.e. if a variable is not declared in a code block, a for initializer,
340      * or a catch parameter.
341      *
342      * @param node the node to check.
343      * @return whether a node is a class field definition.
344      */
345     public static boolean isClassFieldDef(DetailAST node) {
346         return node.getType() == TokenTypes.VARIABLE_DEF
347                 && !isLocalVariableDef(node);
348     }
349 
350     /**
351      * Checks whether ast node is in a specific scope.
352      *
353      * @param ast the node to check.
354      * @param scope a {@code Scope} value.
355      * @return true if the ast node is in the scope.
356      */
357     public static boolean isInScope(DetailAST ast, Scope scope) {
358         final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
359         return surroundingScopeOfAstToken == scope;
360     }
361 
362 }