001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 switch (token.getType()) { 050 case TokenTypes.LITERAL_PUBLIC: 051 result = Scope.PUBLIC; 052 break; 053 case TokenTypes.LITERAL_PROTECTED: 054 result = Scope.PROTECTED; 055 break; 056 case TokenTypes.LITERAL_PRIVATE: 057 result = Scope.PRIVATE; 058 break; 059 default: 060 break; 061 } 062 } 063 return result; 064 } 065 066 /** 067 * Returns the {@code Scope} for a given {@code DetailAST}. 068 * 069 * @param ast the DetailAST to examine 070 * @return a {@code Scope} value 071 */ 072 public static Scope getScope(DetailAST ast) { 073 return Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS)) 074 .map(ScopeUtil::getDeclaredScopeFromMods) 075 .orElseGet(() -> getDefaultScope(ast)); 076 } 077 078 /** 079 * Returns the {@code Scope} specified by the modifier set. If no modifiers are present, 080 * the default scope is used. 081 * 082 * @param aMods root node of a modifier set 083 * @return a {@code Scope} value 084 * @see #getDefaultScope(DetailAST) 085 */ 086 public static Scope getScopeFromMods(DetailAST aMods) { 087 return Optional.ofNullable(getDeclaredScopeFromMods(aMods)) 088 .orElseGet(() -> getDefaultScope(aMods)); 089 } 090 091 /** 092 * Returns the default {@code Scope} for a {@code DetailAST}. 093 * 094 * <p>The following rules are taken into account:</p> 095 * <ul> 096 * <li>enum constants are public</li> 097 * <li>enum constructors are private</li> 098 * <li>interface members are public</li> 099 * <li>everything else is package private</li> 100 * </ul> 101 * 102 * @param ast DetailAST to process 103 * @return a {@code Scope} value 104 */ 105 private static Scope getDefaultScope(DetailAST ast) { 106 final Scope result; 107 if (isInEnumBlock(ast)) { 108 if (ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 109 result = Scope.PUBLIC; 110 } 111 else if (ast.getType() == TokenTypes.CTOR_DEF) { 112 result = Scope.PRIVATE; 113 } 114 else { 115 result = Scope.PACKAGE; 116 } 117 } 118 else if (isInInterfaceOrAnnotationBlock(ast)) { 119 result = Scope.PUBLIC; 120 } 121 else { 122 result = Scope.PACKAGE; 123 } 124 return result; 125 } 126 127 /** 128 * Returns the scope of the surrounding "block". 129 * 130 * @param node the node to return the scope for 131 * @return the Scope of the surrounding block 132 */ 133 public static Scope getSurroundingScope(DetailAST node) { 134 Scope returnValue = null; 135 for (DetailAST token = node; 136 token != null; 137 token = token.getParent()) { 138 final int type = token.getType(); 139 if (TokenUtil.isTypeDeclaration(type)) { 140 final Scope tokenScope = getScope(token); 141 if (returnValue == null || returnValue.isIn(tokenScope)) { 142 returnValue = tokenScope; 143 } 144 } 145 else if (type == TokenTypes.LITERAL_NEW) { 146 returnValue = Scope.ANONINNER; 147 // because Scope.ANONINNER is not in any other Scope 148 break; 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 private 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 && !returnValue; 208 token = token.getParent()) { 209 if (token.getType() == tokenType) { 210 returnValue = true; 211 } 212 else if (token.getType() == TokenTypes.LITERAL_NEW 213 || TokenUtil.isTypeDeclaration(token.getType())) { 214 break; 215 } 216 } 217 218 return returnValue; 219 } 220 221 /** 222 * Returns whether a node is directly contained within an interface or 223 * annotation block. 224 * 225 * @param node the node to check if directly contained within an interface 226 * or annotation block. 227 * @return a {@code boolean} value 228 */ 229 public static boolean isInInterfaceOrAnnotationBlock(DetailAST node) { 230 return isInInterfaceBlock(node) || isInAnnotationBlock(node); 231 } 232 233 /** 234 * Returns whether a node is directly contained within an enum block. 235 * 236 * @param node the node to check if directly contained within an enum block. 237 * @return a {@code boolean} value 238 */ 239 public static boolean isInEnumBlock(DetailAST node) { 240 boolean returnValue = false; 241 242 // Loop up looking for a containing interface block 243 for (DetailAST token = node.getParent(); 244 token != null; token = token.getParent()) { 245 if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF, 246 TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF, 247 TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF)) { 248 returnValue = token.getType() == TokenTypes.ENUM_DEF; 249 break; 250 } 251 } 252 253 return returnValue; 254 } 255 256 /** 257 * Returns whether the scope of a node is restricted to a code block. 258 * A code block is a method or constructor body, an initializer block, or lambda body. 259 * 260 * @param node the node to check 261 * @return a {@code boolean} value 262 */ 263 public static boolean isInCodeBlock(DetailAST node) { 264 boolean returnValue = false; 265 final int[] tokenTypes = { 266 TokenTypes.METHOD_DEF, 267 TokenTypes.CTOR_DEF, 268 TokenTypes.INSTANCE_INIT, 269 TokenTypes.STATIC_INIT, 270 TokenTypes.LAMBDA, 271 TokenTypes.COMPACT_CTOR_DEF, 272 }; 273 274 // Loop up looking for a containing code block 275 for (DetailAST token = node.getParent(); 276 token != null; 277 token = token.getParent()) { 278 if (TokenUtil.isOfType(token, tokenTypes)) { 279 returnValue = true; 280 break; 281 } 282 } 283 284 return returnValue; 285 } 286 287 /** 288 * Returns whether a node is contained in the outermost type block. 289 * 290 * @param node the node to check 291 * @return a {@code boolean} value 292 */ 293 public static boolean isOuterMostType(DetailAST node) { 294 boolean returnValue = true; 295 for (DetailAST parent = node.getParent(); 296 parent != null; 297 parent = parent.getParent()) { 298 if (TokenUtil.isTypeDeclaration(parent.getType())) { 299 returnValue = false; 300 break; 301 } 302 } 303 304 return returnValue; 305 } 306 307 /** 308 * Determines whether a node is a local variable definition. 309 * I.e. if it is declared in a code block, a for initializer, 310 * or a catch parameter. 311 * 312 * @param node the node to check. 313 * @return whether aAST is a local variable definition. 314 */ 315 public static boolean isLocalVariableDef(DetailAST node) { 316 final boolean localVariableDef; 317 // variable declaration? 318 if (node.getType() == TokenTypes.VARIABLE_DEF) { 319 final DetailAST parent = node.getParent(); 320 localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST, 321 TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE); 322 } 323 324 else if (node.getType() == TokenTypes.RESOURCE) { 325 localVariableDef = node.getChildCount() > 1; 326 } 327 328 // catch parameter? 329 else if (node.getType() == TokenTypes.PARAMETER_DEF) { 330 final DetailAST parent = node.getParent(); 331 localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH; 332 } 333 334 else { 335 localVariableDef = false; 336 } 337 338 return localVariableDef; 339 } 340 341 /** 342 * Determines whether a node is a class field definition. 343 * I.e. if a variable is not declared in a code block, a for initializer, 344 * or a catch parameter. 345 * 346 * @param node the node to check. 347 * @return whether a node is a class field definition. 348 */ 349 public static boolean isClassFieldDef(DetailAST node) { 350 return node.getType() == TokenTypes.VARIABLE_DEF 351 && !isLocalVariableDef(node); 352 } 353 354 /** 355 * Checks whether ast node is in a specific scope. 356 * 357 * @param ast the node to check. 358 * @param scope a {@code Scope} value. 359 * @return true if the ast node is in the scope. 360 */ 361 public static boolean isInScope(DetailAST ast, Scope scope) { 362 final Scope surroundingScopeOfAstToken = getSurroundingScope(ast); 363 return surroundingScopeOfAstToken == scope; 364 } 365 366}