View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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                     && blockComment.getParent().getParent().getParent()
172                         .getType() == TokenTypes.OBJBLOCK
173                 || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF)
174                     && blockComment.getParent().getParent().getParent()
175                         .getParent().getType() == TokenTypes.OBJBLOCK;
176     }
177 
178     /**
179      * Node is on constructor.
180      *
181      * @param blockComment DetailAST
182      * @return true if node is before constructor
183      */
184     public static boolean isOnConstructor(DetailAST blockComment) {
185         return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
186                 || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
187                 || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF)
188                 || isOnPlainClassMember(blockComment);
189     }
190 
191     /**
192      * Node is on compact constructor, note that we don't need to check for a plain
193      * token here, since a compact constructor must be public.
194      *
195      * @param blockComment DetailAST
196      * @return true if node is before compact constructor
197      */
198     public static boolean isOnCompactConstructor(DetailAST blockComment) {
199         return isOnPlainToken(blockComment, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.IDENT)
200                 || isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
201                 || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
202     }
203 
204     /**
205      * Node is on enum constant.
206      *
207      * @param blockComment DetailAST
208      * @return true if node is before enum constant
209      */
210     public static boolean isOnEnumConstant(DetailAST blockComment) {
211         final DetailAST parent = blockComment.getParent();
212         boolean result = false;
213         if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
214             final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
215             if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
216                 result = true;
217             }
218         }
219         else if (parent.getType() == TokenTypes.ANNOTATION
220                 && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
221             result = true;
222         }
223 
224         return result;
225     }
226 
227     /**
228      * Node is on annotation field declaration.
229      *
230      * @param blockComment DetailAST
231      * @return true if node is before annotation field
232      */
233     public static boolean isOnAnnotationField(DetailAST blockComment) {
234         return isOnPlainClassMember(blockComment)
235                 || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
236                 || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
237     }
238 
239     /**
240      * Checks that block comment is on specified token without any modifiers.
241      *
242      * @param blockComment block comment start DetailAST
243      * @param parentTokenType parent token type
244      * @param nextTokenType next token type
245      * @return true if block comment is on specified token without modifiers
246      */
247     private static boolean isOnPlainToken(DetailAST blockComment,
248             int parentTokenType, int nextTokenType) {
249         return blockComment.getParent().getType() == parentTokenType
250                 && !getPrevSiblingSkipComments(blockComment).hasChildren()
251                 && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
252     }
253 
254     /**
255      * Checks that block comment is on specified token with modifiers.
256      *
257      * @param blockComment block comment start DetailAST
258      * @param tokenType parent token type
259      * @return true if block comment is on specified token with modifiers
260      */
261     private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
262         return blockComment.getParent().getType() == TokenTypes.MODIFIERS
263                 && blockComment.getParent().getParent().getType() == tokenType
264                 && getPrevSiblingSkipComments(blockComment) == null;
265     }
266 
267     /**
268      * Checks that block comment is on specified token with annotation.
269      *
270      * @param blockComment block comment start DetailAST
271      * @param tokenType parent token type
272      * @return true if block comment is on specified token with annotation
273      */
274     private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
275         return blockComment.getParent().getType() == TokenTypes.ANNOTATION
276                 && getPrevSiblingSkipComments(blockComment.getParent()) == null
277                 && blockComment.getParent().getParent().getParent().getType() == tokenType
278                 && getPrevSiblingSkipComments(blockComment) == null;
279     }
280 
281     /**
282      * Checks that block comment is on specified class member without any modifiers.
283      *
284      * @param blockComment block comment start DetailAST
285      * @return true if block comment is on specified token without modifiers
286      */
287     private static boolean isOnPlainClassMember(DetailAST blockComment) {
288         DetailAST parent = blockComment.getParent();
289         // type could be in fully qualified form, so we go up to Type token
290         while (parent.getType() == TokenTypes.DOT) {
291             parent = parent.getParent();
292         }
293         return (parent.getType() == TokenTypes.TYPE
294                     || parent.getType() == TokenTypes.TYPE_PARAMETERS)
295                 // previous parent sibling is always TokenTypes.MODIFIERS
296                 && !parent.getPreviousSibling().hasChildren()
297                 && parent.getParent().getParent().getType() == TokenTypes.OBJBLOCK;
298     }
299 
300     /**
301      * Get next sibling node skipping any comment nodes.
302      *
303      * @param node current node
304      * @return next sibling
305      */
306     private static DetailAST getNextSiblingSkipComments(DetailAST node) {
307         DetailAST result = node;
308         while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
309                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
310             result = result.getNextSibling();
311         }
312         return result;
313     }
314 
315     /**
316      * Get previous sibling node skipping any comments.
317      *
318      * @param node current node
319      * @return previous sibling
320      */
321     private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
322         DetailAST result = node.getPreviousSibling();
323         while (result != null
324                 && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
325                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
326             result = result.getPreviousSibling();
327         }
328         return result;
329     }
330 
331 }