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.checks.javadoc;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.DetailNode;
024import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
025import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
026
027/**
028 * <div>
029 * Checks if the javadoc has
030 * <a href="https://docs.oracle.com/en/java/javase/14/docs/specs/javadoc/doc-comment-spec.html#leading-asterisks">
031 * leading asterisks</a> on each line.
032 * </div>
033 *
034 * <p>
035 * The check does not require asterisks on the first line, nor on the last line if it is blank.
036 * All other lines in a Javadoc should start with {@code *}, including blank lines and code blocks.
037 * </p>
038 *
039 * @since 8.38
040 */
041@StatelessCheck
042public class JavadocMissingLeadingAsteriskCheck extends AbstractJavadocCheck {
043
044    /**
045     * A key is pointing to the warning message text in "messages.properties"
046     * file.
047     */
048    public static final String MSG_MISSING_ASTERISK = "javadoc.missing.asterisk";
049
050    /**
051     * Creates a new {@code JavadocMissingLeadingAsteriskCheck} instance.
052     */
053    public JavadocMissingLeadingAsteriskCheck() {
054        // no code by default
055    }
056
057    @Override
058    public int[] getRequiredJavadocTokens() {
059        return new int[] {
060            JavadocCommentsTokenTypes.NEWLINE,
061        };
062    }
063
064    @Override
065    public int[] getAcceptableJavadocTokens() {
066        return getRequiredJavadocTokens();
067    }
068
069    @Override
070    public int[] getDefaultJavadocTokens() {
071        return getRequiredJavadocTokens();
072    }
073
074    @Override
075    public void visitJavadocToken(DetailNode detailNode) {
076        if (!isInsideHtmlComment(detailNode)) {
077            final DetailNode nextSibling = detailNode.getNextSibling();
078
079            if (nextSibling != null && !isLeadingAsterisk(nextSibling)
080                        && !isLastLine(nextSibling)) {
081                log(nextSibling.getLineNumber(), MSG_MISSING_ASTERISK);
082            }
083        }
084    }
085
086    /**
087     * Checks whether the given node is inside an HTML comment.
088     *
089     * @param detailNode the node to process
090     * @return {@code true} if the node is inside an HTML comment
091     */
092    private static boolean isInsideHtmlComment(DetailNode detailNode) {
093        final int parentType = detailNode.getParent().getType();
094        return parentType == JavadocCommentsTokenTypes.HTML_COMMENT_CONTENT
095                || parentType == JavadocCommentsTokenTypes.HTML_COMMENT;
096
097    }
098
099    /**
100     * Checks whether the given node is a leading asterisk.
101     *
102     * @param detailNode the node to process
103     * @return {@code true} if the node is {@link JavadocCommentsTokenTypes#LEADING_ASTERISK}
104     */
105    private static boolean isLeadingAsterisk(DetailNode detailNode) {
106        return detailNode.getType() == JavadocCommentsTokenTypes.LEADING_ASTERISK;
107    }
108
109    /**
110     * Checks whether this node is the end of a Javadoc comment,
111     * optionally preceded by blank text.
112     *
113     * @param detailNode the node to process
114     * @return {@code true} if the node is {@code null}
115     */
116    private static boolean isLastLine(DetailNode detailNode) {
117        final DetailNode node;
118        if (CommonUtil.isBlank(detailNode.getText())) {
119            node = detailNode.getNextSibling();
120        }
121        else {
122            node = detailNode;
123        }
124        return node == null;
125    }
126
127}