001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.JavadocTokenTypes;
025import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
026import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
027import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
028
029/**
030 * <div>
031 * Checks if the javadoc has
032 * <a href="https://docs.oracle.com/en/java/javase/14/docs/specs/javadoc/doc-comment-spec.html#leading-asterisks">
033 * leading asterisks</a> on each line.
034 * </div>
035 *
036 * <p>
037 * The check does not require asterisks on the first line, nor on the last line if it is blank.
038 * All other lines in a Javadoc should start with {@code *}, including blank lines and code blocks.
039 * </p>
040 *
041 * @since 8.38
042 */
043@StatelessCheck
044public class JavadocMissingLeadingAsteriskCheck extends AbstractJavadocCheck {
045
046    /**
047     * A key is pointing to the warning message text in "messages.properties"
048     * file.
049     */
050    public static final String MSG_MISSING_ASTERISK = "javadoc.missing.asterisk";
051
052    @Override
053    public int[] getRequiredJavadocTokens() {
054        return new int[] {
055            JavadocTokenTypes.NEWLINE,
056        };
057    }
058
059    @Override
060    public int[] getAcceptableJavadocTokens() {
061        return getRequiredJavadocTokens();
062    }
063
064    @Override
065    public int[] getDefaultJavadocTokens() {
066        return getRequiredJavadocTokens();
067    }
068
069    @Override
070    public void visitJavadocToken(DetailNode detailNode) {
071        DetailNode nextSibling = getNextNode(detailNode);
072
073        // Till https://github.com/checkstyle/checkstyle/issues/9005
074        // Due to bug in the Javadoc parser there may be phantom description nodes.
075        while (TokenUtil.isOfType(nextSibling.getType(),
076                JavadocTokenTypes.DESCRIPTION, JavadocTokenTypes.WS)) {
077            nextSibling = getNextNode(nextSibling);
078        }
079
080        if (!isLeadingAsterisk(nextSibling) && !isLastLine(nextSibling)) {
081            log(nextSibling.getLineNumber(), MSG_MISSING_ASTERISK);
082        }
083    }
084
085    /**
086     * Gets next node in the ast (sibling or parent sibling for the last node).
087     *
088     * @param detailNode the node to process
089     * @return next node.
090     */
091    private static DetailNode getNextNode(DetailNode detailNode) {
092        DetailNode node = JavadocUtil.getFirstChild(detailNode);
093        if (node == null) {
094            node = JavadocUtil.getNextSibling(detailNode);
095            if (node == null) {
096                DetailNode parent = detailNode;
097                do {
098                    parent = parent.getParent();
099                    node = JavadocUtil.getNextSibling(parent);
100                } while (node == null);
101            }
102        }
103        return node;
104    }
105
106    /**
107     * Checks whether the given node is a leading asterisk.
108     *
109     * @param detailNode the node to process
110     * @return {@code true} if the node is {@link JavadocTokenTypes#LEADING_ASTERISK}
111     */
112    private static boolean isLeadingAsterisk(DetailNode detailNode) {
113        return detailNode.getType() == JavadocTokenTypes.LEADING_ASTERISK;
114    }
115
116    /**
117     * Checks whether this node is the end of a Javadoc comment,
118     * optionally preceded by blank text.
119     *
120     * @param detailNode the node to process
121     * @return {@code true} if the node is {@link JavadocTokenTypes#EOF}
122     */
123    private static boolean isLastLine(DetailNode detailNode) {
124        final DetailNode node;
125        if (CommonUtil.isBlank(detailNode.getText())) {
126            node = getNextNode(detailNode);
127        }
128        else {
129            node = detailNode;
130        }
131        return node.getType() == JavadocTokenTypes.EOF;
132    }
133
134}