View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.utils;
21  
22  import com.puppycrawl.tools.checkstyle.api.DetailAST;
23  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24  
25  /**
26   * Utility class that has methods to check javadoc comment position in java file.
27   *
28   */
29  public final class BlockCommentPosition {
30  
31      /**
32       * Forbid new instances.
33       */
34      private BlockCommentPosition() {
35      }
36  
37      /**
38       * Node is on type definition.
39       *
40       * @param blockComment DetailAST
41       * @return true if node is before class, interface, enum or annotation.
42       */
43      public static boolean isOnType(DetailAST blockComment) {
44          return isOnClass(blockComment)
45                  || isOnInterface(blockComment)
46                  || isOnEnum(blockComment)
47                  || isOnAnnotationDef(blockComment)
48                  || isOnRecord(blockComment);
49      }
50  
51      /**
52       * Node is on class definition.
53       *
54       * @param blockComment DetailAST
55       * @return true if node is before class
56       */
57      public static boolean isOnClass(DetailAST blockComment) {
58          return isOnPlainToken(blockComment, TokenTypes.CLASS_DEF, TokenTypes.LITERAL_CLASS)
59                  || isOnTokenWithModifiers(blockComment, TokenTypes.CLASS_DEF)
60                  || isOnTokenWithAnnotation(blockComment, TokenTypes.CLASS_DEF);
61      }
62  
63      /**
64       * Node is on record definition.
65       *
66       * @param blockComment DetailAST
67       * @return true if node is before class
68       */
69      public static boolean isOnRecord(DetailAST blockComment) {
70          return isOnPlainToken(blockComment, TokenTypes.RECORD_DEF, TokenTypes.LITERAL_RECORD)
71              || isOnTokenWithModifiers(blockComment, TokenTypes.RECORD_DEF)
72              || isOnTokenWithAnnotation(blockComment, TokenTypes.RECORD_DEF);
73      }
74  
75      /**
76       * Node is on package definition.
77       *
78       * @param blockComment DetailAST
79       * @return true if node is before package
80       */
81      public static boolean isOnPackage(DetailAST blockComment) {
82          boolean result = isOnTokenWithAnnotation(blockComment, TokenTypes.PACKAGE_DEF);
83  
84          if (!result) {
85              DetailAST nextSibling = blockComment.getNextSibling();
86  
87              while (nextSibling != null
88                      && nextSibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
89                  nextSibling = nextSibling.getNextSibling();
90              }
91  
92              result = nextSibling != null && nextSibling.getType() == TokenTypes.PACKAGE_DEF;
93          }
94  
95          return result;
96      }
97  
98      /**
99       * 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
150      *     or annotation field
151      */
152     public static boolean isOnMember(DetailAST blockComment) {
153         return isOnMethod(blockComment)
154                 || isOnField(blockComment)
155                 || isOnConstructor(blockComment)
156                 || isOnEnumConstant(blockComment)
157                 || isOnAnnotationField(blockComment)
158                 || isOnCompactConstructor(blockComment);
159     }
160 
161     /**
162      * Node is on method declaration.
163      *
164      * @param blockComment DetailAST
165      * @return true if node is before method
166      */
167     public static boolean isOnMethod(DetailAST blockComment) {
168         return isOnPlainClassMember(blockComment)
169                 || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
170                 || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
171     }
172 
173     /**
174      * Node is on field declaration.
175      *
176      * @param blockComment DetailAST
177      * @return true if node is before field
178      */
179     public static boolean isOnField(DetailAST blockComment) {
180         return isOnPlainClassMember(blockComment)
181                 || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
182                     && isTypeMemberContainer(blockComment.getParent().getParent().getParent())
183                 || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF)
184                     && isTypeMemberContainer(
185                         blockComment.getParent().getParent().getParent().getParent());
186     }
187 
188     /**
189      * Node is on constructor.
190      *
191      * @param blockComment DetailAST
192      * @return true if node is before constructor
193      */
194     public static boolean isOnConstructor(DetailAST blockComment) {
195         return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
196                 || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
197                 || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF)
198                 || isOnPlainClassMember(blockComment);
199     }
200 
201     /**
202      * Node is on compact constructor, note that we don't need to check for a plain
203      * token here, since a compact constructor must be public.
204      *
205      * @param blockComment DetailAST
206      * @return true if node is before compact constructor
207      */
208     public static boolean isOnCompactConstructor(DetailAST blockComment) {
209         return isOnPlainToken(blockComment, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.IDENT)
210                 || isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
211                 || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
212     }
213 
214     /**
215      * Node is on enum constant.
216      *
217      * @param blockComment DetailAST
218      * @return true if node is before enum constant
219      */
220     public static boolean isOnEnumConstant(DetailAST blockComment) {
221         final DetailAST parent = blockComment.getParent();
222         boolean result = false;
223         if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
224             final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
225             if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
226                 result = true;
227             }
228         }
229         else if (parent.getType() == TokenTypes.ANNOTATION
230                 && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
231             result = true;
232         }
233 
234         return result;
235     }
236 
237     /**
238      * Node is on annotation field declaration.
239      *
240      * @param blockComment DetailAST
241      * @return true if node is before annotation field
242      */
243     public static boolean isOnAnnotationField(DetailAST blockComment) {
244         return isOnPlainClassMember(blockComment)
245                 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
246                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
247     }
248 
249     /**
250      * Checks that block comment is on module definition without annotations. The
251      * comment must be a direct child of {@code MODULE_DEF} preceded only by the
252      * empty {@code ANNOTATIONS} node, so that a comment placed after the
253      * {@code open} keyword is not accepted.
254      *
255      * @param blockComment block comment start DetailAST
256      * @return true if block comment is on module definition without annotations
257      */
258     private static boolean isOnModuleWithoutAnnotation(DetailAST blockComment) {
259         final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
260         return blockComment.getParent().getType() == TokenTypes.MODULE_DEF
261                 && prevSibling.getType() == TokenTypes.ANNOTATIONS
262                 && !prevSibling.hasChildren();
263     }
264 
265     /**
266      * Checks that block comment is on specified token without any modifiers.
267      *
268      * @param blockComment block comment start DetailAST
269      * @param parentTokenType parent token type
270      * @param nextTokenType next token type
271      * @return true if block comment is on specified token without modifiers
272      */
273     private static boolean isOnPlainToken(DetailAST blockComment,
274             int parentTokenType, int nextTokenType) {
275         return blockComment.getParent().getType() == parentTokenType
276                 && !getPrevSiblingSkipComments(blockComment).hasChildren()
277                 && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
278     }
279 
280     /**
281      * Checks that block comment is on specified token with modifiers.
282      *
283      * @param blockComment block comment start DetailAST
284      * @param tokenType parent token type
285      * @return true if block comment is on specified token with modifiers
286      */
287     private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
288         return blockComment.getParent().getType() == TokenTypes.MODIFIERS
289                 && blockComment.getParent().getParent().getType() == tokenType
290                 && getPrevSiblingSkipComments(blockComment) == null;
291     }
292 
293     /**
294      * Checks that block comment is on specified token with annotation.
295      *
296      * @param blockComment block comment start DetailAST
297      * @param tokenType parent token type
298      * @return true if block comment is on specified token with annotation
299      */
300     private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
301         return blockComment.getParent().getType() == TokenTypes.ANNOTATION
302                 && getPrevSiblingSkipComments(blockComment.getParent()) == null
303                 && blockComment.getParent().getParent().getParent().getType() == tokenType
304                 && getPrevSiblingSkipComments(blockComment) == null;
305     }
306 
307     /**
308      * Checks that block comment is on specified class member without any modifiers.
309      *
310      * @param blockComment block comment start DetailAST
311      * @return true if block comment is on specified token without modifiers
312      */
313     private static boolean isOnPlainClassMember(DetailAST blockComment) {
314         DetailAST parent = blockComment.getParent();
315         // type could be in fully qualified form, so we go up to Type token
316         while (parent.getType() == TokenTypes.DOT) {
317             parent = parent.getParent();
318         }
319         return (parent.getType() == TokenTypes.TYPE
320                     || parent.getType() == TokenTypes.TYPE_PARAMETERS)
321                 // previous parent sibling is always TokenTypes.MODIFIERS
322                 && !parent.getPreviousSibling().hasChildren()
323                 && isTypeMemberContainer(parent.getParent().getParent());
324     }
325 
326     /**
327      * Checks that the block comment's container is a type body or a compact source file,
328      * i.e. a context where a field or member declaration is valid. In a JEP 512 compact
329      * source file, top-level members are children of a {@code COMPACT_COMPILATION_UNIT}
330      * instead of an {@code OBJBLOCK}.
331      *
332      * @param container the AST expected to contain the member
333      * @return true if container is an OBJBLOCK or a compact compilation unit
334      */
335     private static boolean isTypeMemberContainer(DetailAST container) {
336         return container.getType() == TokenTypes.OBJBLOCK
337                 || container.getType() == TokenTypes.COMPACT_COMPILATION_UNIT;
338     }
339 
340     /**
341      * Get next sibling node skipping any comment nodes.
342      *
343      * @param node current node
344      * @return next sibling
345      */
346     private static DetailAST getNextSiblingSkipComments(DetailAST node) {
347         DetailAST result = node;
348         while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
349                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
350             result = result.getNextSibling();
351         }
352         return result;
353     }
354 
355     /**
356      * Get previous sibling node skipping any comments.
357      *
358      * @param node current node
359      * @return previous sibling
360      */
361     private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
362         DetailAST result = node.getPreviousSibling();
363         while (result != null
364                 && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
365                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
366             result = result.getPreviousSibling();
367         }
368         return result;
369     }
370 
371 }