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