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 com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.TokenTypes; 024 025/** 026 * Utility class that has methods to check javadoc comment position in java file. 027 * 028 */ 029public final class BlockCommentPosition { 030 031 /** 032 * Forbid new instances. 033 */ 034 private BlockCommentPosition() { 035 } 036 037 /** 038 * Node is on type definition. 039 * 040 * @param blockComment DetailAST 041 * @return true if node is before class, interface, enum or annotation. 042 */ 043 public static boolean isOnType(DetailAST blockComment) { 044 return isOnClass(blockComment) 045 || isOnInterface(blockComment) 046 || isOnEnum(blockComment) 047 || isOnAnnotationDef(blockComment) 048 || isOnRecord(blockComment); 049 } 050 051 /** 052 * Node is on class definition. 053 * 054 * @param blockComment DetailAST 055 * @return true if node is before class 056 */ 057 public static boolean isOnClass(DetailAST blockComment) { 058 return isOnPlainToken(blockComment, TokenTypes.CLASS_DEF, TokenTypes.LITERAL_CLASS) 059 || isOnTokenWithModifiers(blockComment, TokenTypes.CLASS_DEF) 060 || isOnTokenWithAnnotation(blockComment, TokenTypes.CLASS_DEF); 061 } 062 063 /** 064 * Node is on record definition. 065 * 066 * @param blockComment DetailAST 067 * @return true if node is before class 068 */ 069 public static boolean isOnRecord(DetailAST blockComment) { 070 return isOnPlainToken(blockComment, TokenTypes.RECORD_DEF, TokenTypes.LITERAL_RECORD) 071 || isOnTokenWithModifiers(blockComment, TokenTypes.RECORD_DEF) 072 || isOnTokenWithAnnotation(blockComment, TokenTypes.RECORD_DEF); 073 } 074 075 /** 076 * Node is on package definition. 077 * 078 * @param blockComment DetailAST 079 * @return true if node is before package 080 */ 081 public static boolean isOnPackage(DetailAST blockComment) { 082 boolean result = isOnTokenWithAnnotation(blockComment, TokenTypes.PACKAGE_DEF); 083 084 if (!result) { 085 DetailAST nextSibling = blockComment.getNextSibling(); 086 087 while (nextSibling != null 088 && nextSibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 089 nextSibling = nextSibling.getNextSibling(); 090 } 091 092 result = nextSibling != null && nextSibling.getType() == TokenTypes.PACKAGE_DEF; 093 } 094 095 return result; 096 } 097 098 /** 099 * Node is on module definition. 100 * 101 * @param blockComment DetailAST 102 * @return true if node is before module 103 */ 104 public static boolean isOnModule(DetailAST blockComment) { 105 return isOnModuleWithoutAnnotation(blockComment) 106 || isOnTokenWithAnnotation(blockComment, TokenTypes.MODULE_DEF); 107 } 108 109 /** 110 * Node is on interface definition. 111 * 112 * @param blockComment DetailAST 113 * @return true if node is before interface 114 */ 115 public static boolean isOnInterface(DetailAST blockComment) { 116 return isOnPlainToken(blockComment, TokenTypes.INTERFACE_DEF, TokenTypes.LITERAL_INTERFACE) 117 || isOnTokenWithModifiers(blockComment, TokenTypes.INTERFACE_DEF) 118 || isOnTokenWithAnnotation(blockComment, TokenTypes.INTERFACE_DEF); 119 } 120 121 /** 122 * Node is on enum definition. 123 * 124 * @param blockComment DetailAST 125 * @return true if node is before enum 126 */ 127 public static boolean isOnEnum(DetailAST blockComment) { 128 return isOnPlainToken(blockComment, TokenTypes.ENUM_DEF, TokenTypes.ENUM) 129 || isOnTokenWithModifiers(blockComment, TokenTypes.ENUM_DEF) 130 || isOnTokenWithAnnotation(blockComment, TokenTypes.ENUM_DEF); 131 } 132 133 /** 134 * Node is on annotation definition. 135 * 136 * @param blockComment DetailAST 137 * @return true if node is before annotation 138 */ 139 public static boolean isOnAnnotationDef(DetailAST blockComment) { 140 return isOnPlainToken(blockComment, TokenTypes.ANNOTATION_DEF, TokenTypes.AT) 141 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_DEF) 142 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_DEF); 143 } 144 145 /** 146 * Node is on type member declaration. 147 * 148 * @param blockComment DetailAST 149 * @return true if node is before method, field, constructor, enum constant or annotation field 150 */ 151 public static boolean isOnMember(DetailAST blockComment) { 152 return isOnMethod(blockComment) 153 || isOnField(blockComment) 154 || isOnConstructor(blockComment) 155 || isOnEnumConstant(blockComment) 156 || isOnAnnotationField(blockComment) 157 || isOnCompactConstructor(blockComment); 158 } 159 160 /** 161 * Node is on method declaration. 162 * 163 * @param blockComment DetailAST 164 * @return true if node is before method 165 */ 166 public static boolean isOnMethod(DetailAST blockComment) { 167 return isOnPlainClassMember(blockComment) 168 || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF) 169 || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF); 170 } 171 172 /** 173 * Node is on field declaration. 174 * 175 * @param blockComment DetailAST 176 * @return true if node is before field 177 */ 178 public static boolean isOnField(DetailAST blockComment) { 179 return isOnPlainClassMember(blockComment) 180 || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF) 181 && isTypeMemberContainer(blockComment.getParent().getParent().getParent()) 182 || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF) 183 && isTypeMemberContainer( 184 blockComment.getParent().getParent().getParent().getParent()); 185 } 186 187 /** 188 * Node is on constructor. 189 * 190 * @param blockComment DetailAST 191 * @return true if node is before constructor 192 */ 193 public static boolean isOnConstructor(DetailAST blockComment) { 194 return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT) 195 || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF) 196 || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF) 197 || isOnPlainClassMember(blockComment); 198 } 199 200 /** 201 * Node is on compact constructor, note that we don't need to check for a plain 202 * token here, since a compact constructor must be public. 203 * 204 * @param blockComment DetailAST 205 * @return true if node is before compact constructor 206 */ 207 public static boolean isOnCompactConstructor(DetailAST blockComment) { 208 return isOnPlainToken(blockComment, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.IDENT) 209 || isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF) 210 || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF); 211 } 212 213 /** 214 * Node is on enum constant. 215 * 216 * @param blockComment DetailAST 217 * @return true if node is before enum constant 218 */ 219 public static boolean isOnEnumConstant(DetailAST blockComment) { 220 final DetailAST parent = blockComment.getParent(); 221 boolean result = false; 222 if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 223 final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment); 224 if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) { 225 result = true; 226 } 227 } 228 else if (parent.getType() == TokenTypes.ANNOTATION 229 && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) { 230 result = true; 231 } 232 233 return result; 234 } 235 236 /** 237 * Node is on annotation field declaration. 238 * 239 * @param blockComment DetailAST 240 * @return true if node is before annotation field 241 */ 242 public static boolean isOnAnnotationField(DetailAST blockComment) { 243 return isOnPlainClassMember(blockComment) 244 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF) 245 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF); 246 } 247 248 /** 249 * Checks that block comment is on module definition without annotations. The 250 * comment must be a direct child of {@code MODULE_DEF} preceded only by the 251 * empty {@code ANNOTATIONS} node, so that a comment placed after the 252 * {@code open} keyword is not accepted. 253 * 254 * @param blockComment block comment start DetailAST 255 * @return true if block comment is on module definition without annotations 256 */ 257 private static boolean isOnModuleWithoutAnnotation(DetailAST blockComment) { 258 final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment); 259 return blockComment.getParent().getType() == TokenTypes.MODULE_DEF 260 && prevSibling.getType() == TokenTypes.ANNOTATIONS 261 && !prevSibling.hasChildren(); 262 } 263 264 /** 265 * Checks that block comment is on specified token without any modifiers. 266 * 267 * @param blockComment block comment start DetailAST 268 * @param parentTokenType parent token type 269 * @param nextTokenType next token type 270 * @return true if block comment is on specified token without modifiers 271 */ 272 private static boolean isOnPlainToken(DetailAST blockComment, 273 int parentTokenType, int nextTokenType) { 274 return blockComment.getParent().getType() == parentTokenType 275 && !getPrevSiblingSkipComments(blockComment).hasChildren() 276 && getNextSiblingSkipComments(blockComment).getType() == nextTokenType; 277 } 278 279 /** 280 * Checks that block comment is on specified token with modifiers. 281 * 282 * @param blockComment block comment start DetailAST 283 * @param tokenType parent token type 284 * @return true if block comment is on specified token with modifiers 285 */ 286 private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) { 287 return blockComment.getParent().getType() == TokenTypes.MODIFIERS 288 && blockComment.getParent().getParent().getType() == tokenType 289 && getPrevSiblingSkipComments(blockComment) == null; 290 } 291 292 /** 293 * Checks that block comment is on specified token with annotation. 294 * 295 * @param blockComment block comment start DetailAST 296 * @param tokenType parent token type 297 * @return true if block comment is on specified token with annotation 298 */ 299 private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) { 300 return blockComment.getParent().getType() == TokenTypes.ANNOTATION 301 && getPrevSiblingSkipComments(blockComment.getParent()) == null 302 && blockComment.getParent().getParent().getParent().getType() == tokenType 303 && getPrevSiblingSkipComments(blockComment) == null; 304 } 305 306 /** 307 * Checks that block comment is on specified class member without any modifiers. 308 * 309 * @param blockComment block comment start DetailAST 310 * @return true if block comment is on specified token without modifiers 311 */ 312 private static boolean isOnPlainClassMember(DetailAST blockComment) { 313 DetailAST parent = blockComment.getParent(); 314 // type could be in fully qualified form, so we go up to Type token 315 while (parent.getType() == TokenTypes.DOT) { 316 parent = parent.getParent(); 317 } 318 return (parent.getType() == TokenTypes.TYPE 319 || parent.getType() == TokenTypes.TYPE_PARAMETERS) 320 // previous parent sibling is always TokenTypes.MODIFIERS 321 && !parent.getPreviousSibling().hasChildren() 322 && isTypeMemberContainer(parent.getParent().getParent()); 323 } 324 325 /** 326 * Checks that the block comment's container is a type body or a compact source file, 327 * i.e. a context where a field or member declaration is valid. In a JEP 512 compact 328 * source file, top-level members are children of a {@code COMPACT_COMPILATION_UNIT} 329 * instead of an {@code OBJBLOCK}. 330 * 331 * @param container the AST expected to contain the member 332 * @return true if container is an OBJBLOCK or a compact compilation unit 333 */ 334 private static boolean isTypeMemberContainer(DetailAST container) { 335 return container.getType() == TokenTypes.OBJBLOCK 336 || container.getType() == TokenTypes.COMPACT_COMPILATION_UNIT; 337 } 338 339 /** 340 * Get next sibling node skipping any comment nodes. 341 * 342 * @param node current node 343 * @return next sibling 344 */ 345 private static DetailAST getNextSiblingSkipComments(DetailAST node) { 346 DetailAST result = node; 347 while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT 348 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 349 result = result.getNextSibling(); 350 } 351 return result; 352 } 353 354 /** 355 * Get previous sibling node skipping any comments. 356 * 357 * @param node current node 358 * @return previous sibling 359 */ 360 private static DetailAST getPrevSiblingSkipComments(DetailAST node) { 361 DetailAST result = node.getPreviousSibling(); 362 while (result != null 363 && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT 364 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) { 365 result = result.getPreviousSibling(); 366 } 367 return result; 368 } 369 370}