1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 mods root node of a modifier set
43 * @return a {@code Scope} value or {@code null}
44 */
45 public static Optional<Scope> getDeclaredScopeFromMods(DetailAST mods) {
46 Optional<Scope> result = Optional.empty();
47 for (DetailAST token = mods.getFirstChild(); token != null;
48 token = token.getNextSibling()) {
49 result = switch (token.getType()) {
50 case TokenTypes.LITERAL_PUBLIC -> Optional.of(Scope.PUBLIC);
51 case TokenTypes.LITERAL_PROTECTED -> Optional.of(Scope.PROTECTED);
52 case TokenTypes.LITERAL_PRIVATE -> Optional.of(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 .flatMap(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 mods root node of a modifier set
76 * @return a {@code Scope} value
77 * @see #getDefaultScope(DetailAST)
78 */
79 public static Scope getScopeFromMods(DetailAST mods) {
80 return getDeclaredScopeFromMods(mods)
81 .orElseGet(() -> getDefaultScope(mods));
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 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
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 || parent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) {
296 returnValue = false;
297 break;
298 }
299 }
300
301 return returnValue;
302 }
303
304 /**
305 * Determines whether a node is a local variable definition.
306 * I.e. if it is declared in a code block, a for initializer,
307 * or a catch parameter.
308 *
309 * @param node the node to check.
310 * @return whether aAST is a local variable definition.
311 */
312 public static boolean isLocalVariableDef(DetailAST node) {
313 final boolean localVariableDef;
314 // variable declaration?
315 if (node.getType() == TokenTypes.VARIABLE_DEF) {
316 final DetailAST parent = node.getParent();
317 localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST,
318 TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE);
319 }
320
321 else if (node.getType() == TokenTypes.RESOURCE) {
322 localVariableDef = node.getChildCount() > 1;
323 }
324
325 // catch parameter?
326 else if (node.getType() == TokenTypes.PARAMETER_DEF) {
327 final DetailAST parent = node.getParent();
328 localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH;
329 }
330
331 else {
332 localVariableDef = false;
333 }
334
335 return localVariableDef;
336 }
337
338 /**
339 * Determines whether a node is a class field definition.
340 * I.e. if a variable is not declared in a code block, a for initializer,
341 * or a catch parameter.
342 *
343 * @param node the node to check.
344 * @return whether a node is a class field definition.
345 */
346 public static boolean isClassFieldDef(DetailAST node) {
347 return node.getType() == TokenTypes.VARIABLE_DEF
348 && !isLocalVariableDef(node);
349 }
350
351 /**
352 * Checks whether ast node is in a specific scope.
353 *
354 * @param ast the node to check.
355 * @param scope a {@code Scope} value.
356 * @return true if the ast node is in the scope.
357 */
358 public static boolean isInScope(DetailAST ast, Scope scope) {
359 return getSurroundingScope(ast)
360 .map(surroundingScope -> {
361 return surroundingScope == scope;
362 })
363 .orElse(Boolean.FALSE);
364 }
365
366 }