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              result = switch (token.getType()) {
50                  case TokenTypes.LITERAL_PUBLIC -> Scope.PUBLIC;
51                  case TokenTypes.LITERAL_PROTECTED -> Scope.PROTECTED;
52                  case TokenTypes.LITERAL_PRIVATE -> Scope.PRIVATE;
53                  default -> result;
54              };
55          }
56          return result;
57      }
58  
59      /**
60       * Returns the {@code Scope} for a given {@code DetailAST}.
61       *
62       * @param ast the DetailAST to examine
63       * @return a {@code Scope} value
64       */
65      public static Scope getScope(DetailAST ast) {
66          return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
67                  .map(ScopeUtil::getDeclaredScopeFromMods)
68                  .orElseGet(() -> getDefaultScope(ast));
69      }
70  
71      /**
72       * Returns the {@code Scope} specified by the modifier set. If no modifiers are present,
73       * the default scope is used.
74       *
75       * @param aMods root node of a modifier set
76       * @return a {@code Scope} value
77       * @see #getDefaultScope(DetailAST)
78       */
79      public static Scope getScopeFromMods(DetailAST aMods) {
80          return Optional.ofNullable(getDeclaredScopeFromMods(aMods))
81                  .orElseGet(() -> getDefaultScope(aMods));
82      }
83  
84      /**
85       * Returns the default {@code Scope} for a {@code DetailAST}.
86       *
87       * <p>The following rules are taken into account:</p>
88       * <ul>
89       *     <li>enum constants are public</li>
90       *     <li>enum constructors are private</li>
91       *     <li>interface members are public</li>
92       *     <li>everything else is package private</li>
93       * </ul>
94       *
95       * @param ast DetailAST to process
96       * @return a {@code Scope} value
97       */
98      private static Scope getDefaultScope(DetailAST ast) {
99          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 }