View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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 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                 && parent.getParent().getParent().getType() == TokenTypes.OBJBLOCK;
297     }
298 
299     /**
300      * Get next sibling node skipping any comment nodes.
301      *
302      * @param node current node
303      * @return next sibling
304      */
305     private static DetailAST getNextSiblingSkipComments(DetailAST node) {
306         DetailAST result = node;
307         while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
308                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
309             result = result.getNextSibling();
310         }
311         return result;
312     }
313 
314     /**
315      * Get previous sibling node skipping any comments.
316      *
317      * @param node current node
318      * @return previous sibling
319      */
320     private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
321         DetailAST result = node.getPreviousSibling();
322         while (result != null
323                 && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
324                 || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
325             result = result.getPreviousSibling();
326         }
327         return result;
328     }
329 
330 }