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 isOnPlainModule(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 isOnPlainModule(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}