001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 && result == 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.getParent())); 089 } 090 091 /** 092 * Returns the default {@code Scope} for a {@code DetailAST}. 093 * <p>The following rules are taken into account:</p> 094 * <ul> 095 * <li>enum constants are public</li> 096 * <li>enum constructors are private</li> 097 * <li>interface members are public</li> 098 * <li>everything else is package private</li> 099 * </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.getParent(); 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 && !returnValue; 244 token = token.getParent()) { 245 if (token.getType() == TokenTypes.ENUM_DEF) { 246 returnValue = true; 247 } 248 else if (TokenUtil.isOfType(token, TokenTypes.INTERFACE_DEF, 249 TokenTypes.ANNOTATION_DEF, TokenTypes.CLASS_DEF, 250 TokenTypes.LITERAL_NEW)) { 251 break; 252 } 253 } 254 255 return returnValue; 256 } 257 258 /** 259 * Returns whether the scope of a node is restricted to a code block. 260 * A code block is a method or constructor body, an initializer block, or lambda body. 261 * 262 * @param node the node to check 263 * @return a {@code boolean} value 264 */ 265 public static boolean isInCodeBlock(DetailAST node) { 266 boolean returnValue = false; 267 final int[] tokenTypes = { 268 TokenTypes.METHOD_DEF, 269 TokenTypes.CTOR_DEF, 270 TokenTypes.INSTANCE_INIT, 271 TokenTypes.STATIC_INIT, 272 TokenTypes.LAMBDA, 273 TokenTypes.COMPACT_CTOR_DEF, 274 }; 275 276 // Loop up looking for a containing code block 277 for (DetailAST token = node.getParent(); 278 token != null; 279 token = token.getParent()) { 280 if (TokenUtil.isOfType(token, tokenTypes)) { 281 returnValue = true; 282 break; 283 } 284 } 285 286 return returnValue; 287 } 288 289 /** 290 * Returns whether a node is contained in the outermost type block. 291 * 292 * @param node the node to check 293 * @return a {@code boolean} value 294 */ 295 public static boolean isOuterMostType(DetailAST node) { 296 boolean returnValue = true; 297 for (DetailAST parent = node.getParent(); 298 parent != null; 299 parent = parent.getParent()) { 300 if (TokenUtil.isTypeDeclaration(parent.getType())) { 301 returnValue = false; 302 break; 303 } 304 } 305 306 return returnValue; 307 } 308 309 /** 310 * Determines whether a node is a local variable definition. 311 * I.e. if it is declared in a code block, a for initializer, 312 * or a catch parameter. 313 * 314 * @param node the node to check. 315 * @return whether aAST is a local variable definition. 316 */ 317 public static boolean isLocalVariableDef(DetailAST node) { 318 boolean localVariableDef = false; 319 // variable declaration? 320 if (node.getType() == TokenTypes.VARIABLE_DEF) { 321 final DetailAST parent = node.getParent(); 322 localVariableDef = TokenUtil.isOfType(parent, TokenTypes.SLIST, 323 TokenTypes.FOR_INIT, TokenTypes.FOR_EACH_CLAUSE); 324 } 325 // catch parameter? 326 if (node.getType() == TokenTypes.PARAMETER_DEF) { 327 final DetailAST parent = node.getParent(); 328 localVariableDef = parent.getType() == TokenTypes.LITERAL_CATCH; 329 } 330 331 if (node.getType() == TokenTypes.RESOURCE) { 332 localVariableDef = node.getChildCount() > 1; 333 } 334 return localVariableDef; 335 } 336 337 /** 338 * Determines whether a node is a class field definition. 339 * I.e. if a variable is not declared in a code block, a for initializer, 340 * or a catch parameter. 341 * 342 * @param node the node to check. 343 * @return whether a node is a class field definition. 344 */ 345 public static boolean isClassFieldDef(DetailAST node) { 346 return node.getType() == TokenTypes.VARIABLE_DEF 347 && !isLocalVariableDef(node); 348 } 349 350 /** 351 * Checks whether ast node is in a specific scope. 352 * 353 * @param ast the node to check. 354 * @param scope a {@code Scope} value. 355 * @return true if the ast node is in the scope. 356 */ 357 public static boolean isInScope(DetailAST ast, Scope scope) { 358 final Scope surroundingScopeOfAstToken = getSurroundingScope(ast); 359 return surroundingScopeOfAstToken == scope; 360 } 361 362}