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 interface definition.
100      *
101      * @param blockComment DetailAST
102      * @return true if node is before interface
103      */
104     public static boolean isOnInterface(DetailAST blockComment) {
105         return isOnPlainToken(blockComment, TokenTypes.INTERFACE_DEF, TokenTypes.LITERAL_INTERFACE)
106                 || isOnTokenWithModifiers(blockComment, TokenTypes.INTERFACE_DEF)
107                 || isOnTokenWithAnnotation(blockComment, TokenTypes.INTERFACE_DEF);
108     }
109 
110     /**
111      * Node is on enum definition.
112      *
113      * @param blockComment DetailAST
114      * @return true if node is before enum
115      */
116     public static boolean isOnEnum(DetailAST blockComment) {
117         return isOnPlainToken(blockComment, TokenTypes.ENUM_DEF, TokenTypes.ENUM)
118                 || isOnTokenWithModifiers(blockComment, TokenTypes.ENUM_DEF)
119                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ENUM_DEF);
120     }
121 
122     /**
123      * Node is on annotation definition.
124      *
125      * @param blockComment DetailAST
126      * @return true if node is before annotation
127      */
128     public static boolean isOnAnnotationDef(DetailAST blockComment) {
129         return isOnPlainToken(blockComment, TokenTypes.ANNOTATION_DEF, TokenTypes.AT)
130                 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_DEF)
131                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_DEF);
132     }
133 
134     /**
135      * Node is on type member declaration.
136      *
137      * @param blockComment DetailAST
138      * @return true if node is before method, field, constructor, enum constant
139      *     or annotation field
140      */
141     public static boolean isOnMember(DetailAST blockComment) {
142         return isOnMethod(blockComment)
143                 || isOnField(blockComment)
144                 || isOnConstructor(blockComment)
145                 || isOnEnumConstant(blockComment)
146                 || isOnAnnotationField(blockComment)
147                 || isOnCompactConstructor(blockComment);
148     }
149 
150     /**
151      * Node is on method declaration.
152      *
153      * @param blockComment DetailAST
154      * @return true if node is before method
155      */
156     public static boolean isOnMethod(DetailAST blockComment) {
157         return isOnPlainClassMember(blockComment)
158                 || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
159                 || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
160     }
161 
162     /**
163      * Node is on field declaration.
164      *
165      * @param blockComment DetailAST
166      * @return true if node is before field
167      */
168     public static boolean isOnField(DetailAST blockComment) {
169         return isOnPlainClassMember(blockComment)
170                 || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
171                     && isTypeMemberContainer(blockComment.getParent().getParent().getParent())
172                 || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF)
173                     && isTypeMemberContainer(
174                         blockComment.getParent().getParent().getParent().getParent());
175     }
176 
177     /**
178      * Node is on constructor.
179      *
180      * @param blockComment DetailAST
181      * @return true if node is before constructor
182      */
183     public static boolean isOnConstructor(DetailAST blockComment) {
184         return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
185                 || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
186                 || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF)
187                 || isOnPlainClassMember(blockComment);
188     }
189 
190     /**
191      * Node is on compact constructor, note that we don't need to check for a plain
192      * token here, since a compact constructor must be public.
193      *
194      * @param blockComment DetailAST
195      * @return true if node is before compact constructor
196      */
197     public static boolean isOnCompactConstructor(DetailAST blockComment) {
198         return isOnPlainToken(blockComment, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.IDENT)
199                 || isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
200                 || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
201     }
202 
203     /**
204      * Node is on enum constant.
205      *
206      * @param blockComment DetailAST
207      * @return true if node is before enum constant
208      */
209     public static boolean isOnEnumConstant(DetailAST blockComment) {
210         final DetailAST parent = blockComment.getParent();
211         boolean result = false;
212         if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
213             final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
214             if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
215                 result = true;
216             }
217         }
218         else if (parent.getType() == TokenTypes.ANNOTATION
219                 && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
220             result = true;
221         }
222 
223         return result;
224     }
225 
226     /**
227      * Node is on annotation field declaration.
228      *
229      * @param blockComment DetailAST
230      * @return true if node is before annotation field
231      */
232     public static boolean isOnAnnotationField(DetailAST blockComment) {
233         return isOnPlainClassMember(blockComment)
234                 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
235                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
236     }
237 
238     /**
239      * Checks that block comment is on specified token without any modifiers.
240      *
241      * @param blockComment block comment start DetailAST
242      * @param parentTokenType parent token type
243      * @param nextTokenType next token type
244      * @return true if block comment is on specified token without modifiers
245      */
246     private static boolean isOnPlainToken(DetailAST blockComment,
247             int parentTokenType, int nextTokenType) {
248         return blockComment.getParent().getType() == parentTokenType
249                 && !getPrevSiblingSkipComments(blockComment).hasChildren()
250                 && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
251     }
252 
253     /**
254      * Checks that block comment is on specified token with modifiers.
255      *
256      * @param blockComment block comment start DetailAST
257      * @param tokenType parent token type
258      * @return true if block comment is on specified token with modifiers
259      */
260     private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
261         return blockComment.getParent().getType() == TokenTypes.MODIFIERS
262                 && blockComment.getParent().getParent().getType() == tokenType
263                 && getPrevSiblingSkipComments(blockComment) == null;
264     }
265 
266     /**
267      * Checks that block comment is on specified token with annotation.
268      *
269      * @param blockComment block comment start DetailAST
270      * @param tokenType parent token type
271      * @return true if block comment is on specified token with annotation
272      */
273     private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
274         return blockComment.getParent().getType() == TokenTypes.ANNOTATION
275                 && getPrevSiblingSkipComments(blockComment.getParent()) == null
276                 && blockComment.getParent().getParent().getParent().getType() == tokenType
277                 && getPrevSiblingSkipComments(blockComment) == null;
278     }
279 
280     /**
281      * Checks that block comment is on specified class member without any modifiers.
282      *
283      * @param blockComment block comment start DetailAST
284      * @return true if block comment is on specified token without modifiers
285      */
286     private static boolean isOnPlainClassMember(DetailAST blockComment) {
287         DetailAST parent = blockComment.getParent();
288         // type could be in fully qualified form, so we go up to Type token
289         while (parent.getType() == TokenTypes.DOT) {
290             parent = parent.getParent();
291         }
292         return (parent.getType() == TokenTypes.TYPE
293                     || parent.getType() == TokenTypes.TYPE_PARAMETERS)
294                 // previous parent sibling is always TokenTypes.MODIFIERS
295                 && !parent.getPreviousSibling().hasChildren()
296                 && isTypeMemberContainer(parent.getParent().getParent());
297     }
298 
299     /**
300      * Checks that the block comment's container is a type body or a compact source file,
301      * i.e. a context where a field or member declaration is valid. In a JEP 512 compact
302      * source file, top-level members are children of a {@code COMPACT_COMPILATION_UNIT}
303      * instead of an {@code OBJBLOCK}.
304      *
305      * @param container the AST expected to contain the member
306      * @return true if container is an OBJBLOCK or a compact compilation unit
307      */
308     private static boolean isTypeMemberContainer(DetailAST container) {
309         return container.getType() == TokenTypes.OBJBLOCK
310                 || container.getType() == TokenTypes.COMPACT_COMPILATION_UNIT;
311     }
312 
313     /**
314      * Get next sibling node skipping any comment nodes.
315      *
316      * @param node current node
317      * @return next sibling
318      */
319     private static DetailAST getNextSiblingSkipComments(DetailAST node) {
320         DetailAST result = node;
321         while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
322                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
323             result = result.getNextSibling();
324         }
325         return result;
326     }
327 
328     /**
329      * Get previous sibling node skipping any comments.
330      *
331      * @param node current node
332      * @return previous sibling
333      */
334     private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
335         DetailAST result = node.getPreviousSibling();
336         while (result != null
337                 && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
338                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
339             result = result.getPreviousSibling();
340         }
341         return result;
342     }
343 
344 }