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 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 or annotation field
150     */
151    public static boolean isOnMember(DetailAST blockComment) {
152        return isOnMethod(blockComment)
153                || isOnField(blockComment)
154                || isOnConstructor(blockComment)
155                || isOnEnumConstant(blockComment)
156                || isOnAnnotationField(blockComment)
157                || isOnCompactConstructor(blockComment);
158    }
159
160    /**
161     * Node is on method declaration.
162     *
163     * @param blockComment DetailAST
164     * @return true if node is before method
165     */
166    public static boolean isOnMethod(DetailAST blockComment) {
167        return isOnPlainClassMember(blockComment)
168                || isOnTokenWithModifiers(blockComment, TokenTypes.METHOD_DEF)
169                || isOnTokenWithAnnotation(blockComment, TokenTypes.METHOD_DEF);
170    }
171
172    /**
173     * Node is on field declaration.
174     *
175     * @param blockComment DetailAST
176     * @return true if node is before field
177     */
178    public static boolean isOnField(DetailAST blockComment) {
179        return isOnPlainClassMember(blockComment)
180                || isOnTokenWithModifiers(blockComment, TokenTypes.VARIABLE_DEF)
181                    && isTypeMemberContainer(blockComment.getParent().getParent().getParent())
182                || isOnTokenWithAnnotation(blockComment, TokenTypes.VARIABLE_DEF)
183                    && isTypeMemberContainer(
184                        blockComment.getParent().getParent().getParent().getParent());
185    }
186
187    /**
188     * Node is on constructor.
189     *
190     * @param blockComment DetailAST
191     * @return true if node is before constructor
192     */
193    public static boolean isOnConstructor(DetailAST blockComment) {
194        return isOnPlainToken(blockComment, TokenTypes.CTOR_DEF, TokenTypes.IDENT)
195                || isOnTokenWithModifiers(blockComment, TokenTypes.CTOR_DEF)
196                || isOnTokenWithAnnotation(blockComment, TokenTypes.CTOR_DEF)
197                || isOnPlainClassMember(blockComment);
198    }
199
200    /**
201     * Node is on compact constructor, note that we don't need to check for a plain
202     * token here, since a compact constructor must be public.
203     *
204     * @param blockComment DetailAST
205     * @return true if node is before compact constructor
206     */
207    public static boolean isOnCompactConstructor(DetailAST blockComment) {
208        return isOnPlainToken(blockComment, TokenTypes.COMPACT_CTOR_DEF, TokenTypes.IDENT)
209                || isOnTokenWithModifiers(blockComment, TokenTypes.COMPACT_CTOR_DEF)
210                || isOnTokenWithAnnotation(blockComment, TokenTypes.COMPACT_CTOR_DEF);
211    }
212
213    /**
214     * Node is on enum constant.
215     *
216     * @param blockComment DetailAST
217     * @return true if node is before enum constant
218     */
219    public static boolean isOnEnumConstant(DetailAST blockComment) {
220        final DetailAST parent = blockComment.getParent();
221        boolean result = false;
222        if (parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
223            final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
224            if (prevSibling.getType() == TokenTypes.ANNOTATIONS && !prevSibling.hasChildren()) {
225                result = true;
226            }
227        }
228        else if (parent.getType() == TokenTypes.ANNOTATION
229                && parent.getParent().getParent().getType() == TokenTypes.ENUM_CONSTANT_DEF) {
230            result = true;
231        }
232
233        return result;
234    }
235
236    /**
237     * Node is on annotation field declaration.
238     *
239     * @param blockComment DetailAST
240     * @return true if node is before annotation field
241     */
242    public static boolean isOnAnnotationField(DetailAST blockComment) {
243        return isOnPlainClassMember(blockComment)
244                || isOnTokenWithModifiers(blockComment, TokenTypes.ANNOTATION_FIELD_DEF)
245                || isOnTokenWithAnnotation(blockComment, TokenTypes.ANNOTATION_FIELD_DEF);
246    }
247
248    /**
249     * Checks that block comment is on module definition without annotations. The
250     * comment must be a direct child of {@code MODULE_DEF} preceded only by the
251     * empty {@code ANNOTATIONS} node, so that a comment placed after the
252     * {@code open} keyword is not accepted.
253     *
254     * @param blockComment block comment start DetailAST
255     * @return true if block comment is on module definition without annotations
256     */
257    private static boolean isOnModuleWithoutAnnotation(DetailAST blockComment) {
258        final DetailAST prevSibling = getPrevSiblingSkipComments(blockComment);
259        return blockComment.getParent().getType() == TokenTypes.MODULE_DEF
260                && prevSibling.getType() == TokenTypes.ANNOTATIONS
261                && !prevSibling.hasChildren();
262    }
263
264    /**
265     * Checks that block comment is on specified token without any modifiers.
266     *
267     * @param blockComment block comment start DetailAST
268     * @param parentTokenType parent token type
269     * @param nextTokenType next token type
270     * @return true if block comment is on specified token without modifiers
271     */
272    private static boolean isOnPlainToken(DetailAST blockComment,
273            int parentTokenType, int nextTokenType) {
274        return blockComment.getParent().getType() == parentTokenType
275                && !getPrevSiblingSkipComments(blockComment).hasChildren()
276                && getNextSiblingSkipComments(blockComment).getType() == nextTokenType;
277    }
278
279    /**
280     * Checks that block comment is on specified token with modifiers.
281     *
282     * @param blockComment block comment start DetailAST
283     * @param tokenType parent token type
284     * @return true if block comment is on specified token with modifiers
285     */
286    private static boolean isOnTokenWithModifiers(DetailAST blockComment, int tokenType) {
287        return blockComment.getParent().getType() == TokenTypes.MODIFIERS
288                && blockComment.getParent().getParent().getType() == tokenType
289                && getPrevSiblingSkipComments(blockComment) == null;
290    }
291
292    /**
293     * Checks that block comment is on specified token with annotation.
294     *
295     * @param blockComment block comment start DetailAST
296     * @param tokenType parent token type
297     * @return true if block comment is on specified token with annotation
298     */
299    private static boolean isOnTokenWithAnnotation(DetailAST blockComment, int tokenType) {
300        return blockComment.getParent().getType() == TokenTypes.ANNOTATION
301                && getPrevSiblingSkipComments(blockComment.getParent()) == null
302                && blockComment.getParent().getParent().getParent().getType() == tokenType
303                && getPrevSiblingSkipComments(blockComment) == null;
304    }
305
306    /**
307     * Checks that block comment is on specified class member without any modifiers.
308     *
309     * @param blockComment block comment start DetailAST
310     * @return true if block comment is on specified token without modifiers
311     */
312    private static boolean isOnPlainClassMember(DetailAST blockComment) {
313        DetailAST parent = blockComment.getParent();
314        // type could be in fully qualified form, so we go up to Type token
315        while (parent.getType() == TokenTypes.DOT) {
316            parent = parent.getParent();
317        }
318        return (parent.getType() == TokenTypes.TYPE
319                    || parent.getType() == TokenTypes.TYPE_PARAMETERS)
320                // previous parent sibling is always TokenTypes.MODIFIERS
321                && !parent.getPreviousSibling().hasChildren()
322                && isTypeMemberContainer(parent.getParent().getParent());
323    }
324
325    /**
326     * Checks that the block comment's container is a type body or a compact source file,
327     * i.e. a context where a field or member declaration is valid. In a JEP 512 compact
328     * source file, top-level members are children of a {@code COMPACT_COMPILATION_UNIT}
329     * instead of an {@code OBJBLOCK}.
330     *
331     * @param container the AST expected to contain the member
332     * @return true if container is an OBJBLOCK or a compact compilation unit
333     */
334    private static boolean isTypeMemberContainer(DetailAST container) {
335        return container.getType() == TokenTypes.OBJBLOCK
336                || container.getType() == TokenTypes.COMPACT_COMPILATION_UNIT;
337    }
338
339    /**
340     * Get next sibling node skipping any comment nodes.
341     *
342     * @param node current node
343     * @return next sibling
344     */
345    private static DetailAST getNextSiblingSkipComments(DetailAST node) {
346        DetailAST result = node;
347        while (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
348                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
349            result = result.getNextSibling();
350        }
351        return result;
352    }
353
354    /**
355     * Get previous sibling node skipping any comments.
356     *
357     * @param node current node
358     * @return previous sibling
359     */
360    private static DetailAST getPrevSiblingSkipComments(DetailAST node) {
361        DetailAST result = node.getPreviousSibling();
362        while (result != null
363                && (result.getType() == TokenTypes.SINGLE_LINE_COMMENT
364                || result.getType() == TokenTypes.BLOCK_COMMENT_BEGIN)) {
365            result = result.getPreviousSibling();
366        }
367        return result;
368    }
369
370}