001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 aMods root node of a modifier set 043 * @return a {@code Scope} value or {@code null} 044 */ 045 public static Scope getDeclaredScopeFromMods(DetailAST aMods) { 046 Scope result = null; 047 for (DetailAST token = aMods.getFirstChild(); token != null; 048 token = token.getNextSibling()) { 049 result = switch (token.getType()) { 050 case TokenTypes.LITERAL_PUBLIC -> Scope.PUBLIC; 051 case TokenTypes.LITERAL_PROTECTED -> Scope.PROTECTED; 052 case TokenTypes.LITERAL_PRIVATE -> 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 .map(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 aMods root node of a modifier set 076 * @return a {@code Scope} value 077 * @see #getDefaultScope(DetailAST) 078 */ 079 public static Scope getScopeFromMods(DetailAST aMods) { 080 return Optional.ofNullable(getDeclaredScopeFromMods(aMods)) 081 .orElseGet(() -> getDefaultScope(aMods)); 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 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}