View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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       * <p>The following rules are taken into account:</p>
94       * <ul>
95       *     <li>enum constants are public</li>
96       *     <li>enum constructors are private</li>
97       *     <li>interface members are public</li>
98       *     <li>everything else is package private</li>
99       * </ul>
100      *
101      * @param ast DetailAST to process
102      * @return a {@code Scope} value
103      */
104     private static Scope getDefaultScope(DetailAST ast) {
105         final Scope result;
106         if (isInEnumBlock(ast)) {
107             if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
108                 result = Scope.PUBLIC;
109             }
110             else if (ast.getType() == TokenTypes.CTOR_DEF) {
111                 result = Scope.PRIVATE;
112             }
113             else {
114                 result = Scope.PACKAGE;
115             }
116         }
117         else if (isInInterfaceOrAnnotationBlock(ast)) {
118             result = Scope.PUBLIC;
119         }
120         else {
121             result = Scope.PACKAGE;
122         }
123         return result;
124     }
125 
126     /**
127      * Returns the scope of the surrounding "block".
128      *
129      * @param node the node to return the scope for
130      * @return the Scope of the surrounding block
131      */
132     public static Scope getSurroundingScope(DetailAST node) {
133         Scope returnValue = null;
134         for (DetailAST token = node;
135              token != null;
136              token = token.getParent()) {
137             final int type = token.getType();
138             if (TokenUtil.isTypeDeclaration(type)) {
139                 final Scope tokenScope = getScope(token);
140                 if (returnValue == null || returnValue.isIn(tokenScope)) {
141                     returnValue = tokenScope;
142                 }
143             }
144             else if (type == TokenTypes.LITERAL_NEW) {
145                 returnValue = Scope.ANONINNER;
146                 // because Scope.ANONINNER is not in any other Scope
147                 break;
148             }
149         }
150 
151         return returnValue;
152     }
153 
154     /**
155      * Returns whether a node is directly contained within a class block.
156      *
157      * @param node the node to check if directly contained within a class block.
158      * @return a {@code boolean} value
159      */
160     public static boolean isInClassBlock(DetailAST node) {
161         return isInBlockOf(node, TokenTypes.CLASS_DEF);
162     }
163 
164     /**
165      * Returns whether a node is directly contained within a record block.
166      *
167      * @param node the node to check if directly contained within a record block.
168      * @return a {@code boolean} value
169      */
170     public static boolean isInRecordBlock(DetailAST node) {
171         return isInBlockOf(node, TokenTypes.RECORD_DEF);
172     }
173 
174     /**
175      * Returns whether a node is directly contained within an interface block.
176      *
177      * @param node the node to check if directly contained within an interface block.
178      * @return a {@code boolean} value
179      */
180     public static boolean isInInterfaceBlock(DetailAST node) {
181         return isInBlockOf(node, TokenTypes.INTERFACE_DEF);
182     }
183 
184     /**
185      * Returns whether a node is directly contained within an annotation block.
186      *
187      * @param node the node to check if directly contained within an annotation block.
188      * @return a {@code boolean} value
189      */
190     public static boolean isInAnnotationBlock(DetailAST node) {
191         return isInBlockOf(node, TokenTypes.ANNOTATION_DEF);
192     }
193 
194     /**
195      * Returns whether a node is directly contained within a specified block.
196      *
197      * @param node the node to check if directly contained within a specified block.
198      * @param tokenType type of token.
199      * @return a {@code boolean} value
200      */
201     private static boolean isInBlockOf(DetailAST node, int tokenType) {
202         boolean returnValue = false;
203 
204         // Loop up looking for a containing interface block
205         for (DetailAST token = node.getParent();
206              token != null && !returnValue;
207              token = token.getParent()) {
208             if (token.getType() == tokenType) {
209                 returnValue = true;
210             }
211             else if (token.getType() == TokenTypes.LITERAL_NEW
212                     || TokenUtil.isTypeDeclaration(token.getType())) {
213                 break;
214             }
215         }
216 
217         return returnValue;
218     }
219 
220     /**
221      * Returns whether a node is directly contained within an interface or
222      * annotation block.
223      *
224      * @param node the node to check if directly contained within an interface
225      *     or annotation block.
226      * @return a {@code boolean} value
227      */
228     public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) {
229         return isInInterfaceBlock(node) || isInAnnotationBlock(node);
230     }
231 
232     /**
233      * Returns whether a node is directly contained within an enum block.
234      *
235      * @param node the node to check if directly contained within an enum block.
236      * @return a {@code boolean} value
237      */
238     public static boolean isInEnumBlock(DetailAST node) {
239         boolean returnValue = false;
240 
241         // Loop up looking for a containing interface block
242         for (DetailAST token = node.getParent();
243              token != null; token = token.getParent()) {
244             if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF,
245                 TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF,
246                 TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) {
247                 returnValue = token.getType() == TokenTypes.ENUM_DEF;
248                 break;
249             }
250         }
251 
252         return returnValue;
253     }
254 
255     /**
256      * Returns whether the scope of a node is restricted to a code block.
257      * A code block is a method or constructor body, an initializer block, or lambda body.
258      *
259      * @param node the node to check
260      * @return a {@code boolean} value
261      */
262     public static boolean isInCodeBlock(DetailAST node) {
263         boolean returnValue = false;
264         final int[] tokenTypes = {
265             TokenTypes.METHOD_DEF,
266             TokenTypes.CTOR_DEF,
267             TokenTypes.INSTANCE_INIT,
268             TokenTypes.STATIC_INIT,
269             TokenTypes.LAMBDA,
270             TokenTypes.COMPACT_CTOR_DEF,
271         };
272 
273         // Loop up looking for a containing code block
274         for (DetailAST token = node.getParent();
275              token != null;
276              token = token.getParent()) {
277             if (TokenUtil.isOfType(token, tokenTypes)) {
278                 returnValue = true;
279                 break;
280             }
281         }
282 
283         return returnValue;
284     }
285 
286     /**
287      * Returns whether a node is contained in the outermost type block.
288      *
289      * @param node the node to check
290      * @return a {@code boolean} value
291      */
292     public static boolean isOuterMostType(DetailAST node) {
293         boolean returnValue = true;
294         for (DetailAST parent = node.getParent();
295              parent != null;
296              parent = parent.getParent()) {
297             if (TokenUtil.isTypeDeclaration(parent.getType())) {
298                 returnValue = false;
299                 break;
300             }
301         }
302 
303         return returnValue;
304     }
305 
306     /**
307      * Determines whether a node is a local variable definition.
308      * I.e. if it is declared in a code block, a for initializer,
309      * or a catch parameter.
310      *
311      * @param node the node to check.
312      * @return whether aAST is a local variable definition.
313      */
314     public static boolean isLocalVariableDef(DetailAST node) {
315         final boolean localVariableDef;
316         // variable declaration?
317         if (node.getType() == TokenTypes.VARIABLE_DEF) {
318             final DetailAST parent = node.getParent();
319             localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
320                                 TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
321         }
322 
323         else if (node.getType() == TokenTypes.RESOURCE) {
324             localVariableDef = node.getChildCount() > 1;
325         }
326 
327         // catch parameter?
328         else if (node.getType() == TokenTypes.PARAMETER_DEF) {
329             final DetailAST parent = node.getParent();
330             localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
331         }
332 
333         else {
334             localVariableDef = false;
335         }
336 
337         return localVariableDef;
338     }
339 
340     /**
341      * Determines whether a node is a class field definition.
342      * I.e. if a variable is not declared in a code block, a for initializer,
343      * or a catch parameter.
344      *
345      * @param node the node to check.
346      * @return whether a node is a class field definition.
347      */
348     public static boolean isClassFieldDef(DetailAST node) {
349         return node.getType() == TokenTypes.VARIABLE_DEF
350                 && !isLocalVariableDef(node);
351     }
352 
353     /**
354      * Checks whether ast node is in a specific scope.
355      *
356      * @param ast the node to check.
357      * @param scope a {@code Scope} value.
358      * @return true if the ast node is in the scope.
359      */
360     public static boolean isInScope(DetailAST ast, Scope scope) {
361         final Scope surroundingScopeOfAstToken = getSurroundingScope(ast);
362         return surroundingScopeOfAstToken == scope;
363     }
364 
365 }