001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 java.util.Optional; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.Scope; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027 028/** 029 * Contains utility methods for working on scope. 030 * 031 */ 032public final class ScopeUtil { 033 034 /** Prevent instantiation. */ 035 private ScopeUtil() { 036 } 037 038 /** 039 * Returns the {@code Scope} explicitly specified by the modifier set. 040 * Returns {@code null} if there are no modifiers. 041 * 042 * @param mods root node of a modifier set 043 * @return a {@code Scope} value or {@code null} 044 */ 045 public static Optional<Scope> getDeclaredScopeFromMods(DetailAST mods) { 046 Optional<Scope> result = Optional.empty(); 047 for (DetailAST token = mods.getFirstChild(); token != null; 048 token = token.getNextSibling()) { 049 result = switch (token.getType()) { 050 case TokenTypes.LITERAL_PUBLIC -> Optional.of(Scope.PUBLIC); 051 case TokenTypes.LITERAL_PROTECTED -> Optional.of(Scope.PROTECTED); 052 case TokenTypes.LITERAL_PRIVATE -> Optional.of(Scope.PRIVATE); 053 default -> result; 054 }; 055 } 056 return result; 057 } 058 059 /** 060 * Returns the {@code Scope} for a given {@code DetailAST}. 061 * 062 * @param ast the DetailAST to examine 063 * @return a {@code Scope} value 064 */ 065 public static Scope getScope(DetailAST ast) { 066 return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS)) 067 .flatMap(ScopeUtil::getDeclaredScopeFromMods) 068 .orElseGet(() -> getDefaultScope(ast)); 069 } 070 071 /** 072 * Returns the {@code Scope} specified by the modifier set. If no modifiers are present, 073 * the default scope is used. 074 * 075 * @param mods root node of a modifier set 076 * @return a {@code Scope} value 077 * @see #getDefaultScope(DetailAST) 078 */ 079 public static Scope getScopeFromMods(DetailAST mods) { 080 return getDeclaredScopeFromMods(mods) 081 .orElseGet(() -> getDefaultScope(mods)); 082 } 083 084 /** 085 * Returns the default {@code Scope} for a {@code DetailAST}. 086 * 087 * <p>The following rules are taken into account:</p> 088 * <ul> 089 * <li>enum constants are public</li> 090 * <li>enum constructors are private</li> 091 * <li>interface members are public</li> 092 * <li>everything else is package private</li> 093 * </ul> 094 * 095 * @param ast DetailAST to process 096 * @return a {@code Scope} value 097 */ 098 private static Scope getDefaultScope(DetailAST ast) { 099 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 annotation block. 219 * 220 * @param node the node to check if directly contained within an interface or annotation block. 221 * @return a {@code boolean} value 222 */ 223 public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) { 224 return isInInterfaceBlock(node) || isInAnnotationBlock(node); 225 } 226 227 /** 228 * Returns whether a node is directly contained within an enum block. 229 * 230 * @param node the node to check if directly contained within an enum block. 231 * @return a {@code boolean} value 232 */ 233 public static boolean isInEnumBlock(DetailAST node) { 234 boolean returnValue = false; 235 236 // Loop up looking for a containing interface block 237 for (DetailAST token = node.getParent(); 238 token != null; token = token.getParent()) { 239 if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF, 240 TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF, 241 TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) { 242 returnValue = token.getType() == TokenTypes.ENUM_DEF; 243 break; 244 } 245 } 246 247 return returnValue; 248 } 249 250 /** 251 * Returns whether the scope of a node is restricted to a code block. 252 * A code block is a method or constructor body, an initializer block, or lambda body. 253 * 254 * @param node the node to check 255 * @return a {@code boolean} value 256 */ 257 public static boolean isInCodeBlock(DetailAST node) { 258 boolean returnValue = false; 259 final int[] tokenTypes = { 260 TokenTypes.METHOD_DEF, 261 TokenTypes.CTOR_DEF, 262 TokenTypes.INSTANCE_INIT, 263 TokenTypes.STATIC_INIT, 264 TokenTypes.LAMBDA, 265 TokenTypes.COMPACT_CTOR_DEF, 266 }; 267 268 // Loop up looking for a containing code block 269 for (DetailAST token = node.getParent(); 270 token != null; 271 token = token.getParent()) { 272 if (TokenUtil.isOfType(token, tokenTypes)) { 273 returnValue = true; 274 break; 275 } 276 } 277 278 return returnValue; 279 } 280 281 /** 282 * Returns whether a node is contained in the outermost type block. 283 * 284 * @param node the node to check 285 * @return a {@code boolean} value 286 */ 287 public static boolean isOuterMostType(DetailAST node) { 288 boolean returnValue = true; 289 for (DetailAST parent = node.getParent(); 290 parent != null; 291 parent = parent.getParent()) { 292 if (TokenUtil.isTypeDeclaration(parent.getType()) 293 || parent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT) { 294 returnValue = false; 295 break; 296 } 297 } 298 299 return returnValue; 300 } 301 302 /** 303 * Determines whether a node is a local variable definition. 304 * I.e. if it is declared in a code block, a for initializer, 305 * or a catch parameter. 306 * 307 * @param node the node to check. 308 * @return whether aAST is a local variable definition. 309 */ 310 public static boolean isLocalVariableDef(DetailAST node) { 311 final boolean localVariableDef; 312 // variable declaration? 313 if (node.getType() == TokenTypes.VARIABLE_DEF) { 314 final DetailAST parent = node.getParent(); 315 localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST, 316 TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE); 317 } 318 319 else if (node.getType() == TokenTypes.RESOURCE) { 320 localVariableDef = node.getChildCount() > 1; 321 } 322 323 // catch parameter? 324 else if (node.getType() == TokenTypes.PARAMETER_DEF) { 325 final DetailAST parent = node.getParent(); 326 localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH; 327 } 328 329 else { 330 localVariableDef = false; 331 } 332 333 return localVariableDef; 334 } 335 336 /** 337 * Determines whether a node is a class field definition. 338 * I.e. if a variable is not declared in a code block, a for initializer, 339 * or a catch parameter. 340 * 341 * @param node the node to check. 342 * @return whether a node is a class field definition. 343 */ 344 public static boolean isClassFieldDef(DetailAST node) { 345 return node.getType() == TokenTypes.VARIABLE_DEF 346 && !isLocalVariableDef(node); 347 } 348 349 /** 350 * Checks whether ast node is in a specific scope. 351 * 352 * @param ast the node to check. 353 * @param scope a {@code Scope} value. 354 * @return true if the ast node is in the scope. 355 */ 356 public static boolean isInScope(DetailAST ast, Scope scope) { 357 return getSurroundingScope(ast) 358 .map(surroundingScope -> { 359 return surroundingScope == scope; 360 }) 361 .orElse(Boolean.FALSE); 362 } 363 364}