001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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 * <p>
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 * </p>
035 * <p>
036 * The check does not require asterisks on the first line, nor on the last line if it is blank.
037 * All other lines in a Javadoc should start with {@code *}, including blank lines and code blocks.
038 * </p>
039 * <ul>
040 * <li>
041 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations if the
042 * Javadoc being examined by this check violates the tight html rules defined at
043 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
044 * Type is {@code boolean}.
045 * Default value is {@code false}.
046 * </li>
047 * </ul>
048 * <p>
049 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
050 * </p>
051 * <p>
052 * Violation Message Keys:
053 * </p>
054 * <ul>
055 * <li>
056 * {@code javadoc.missed.html.close}
057 * </li>
058 * <li>
059 * {@code javadoc.missing.asterisk}
060 * </li>
061 * <li>
062 * {@code javadoc.parse.rule.error}
063 * </li>
064 * <li>
065 * {@code javadoc.wrong.singleton.html.tag}
066 * </li>
067 * </ul>
068 *
069 * @since 8.38
070 */
071@StatelessCheck
072public class JavadocMissingLeadingAsteriskCheck extends AbstractJavadocCheck {
073
074    /**
075     * A key is pointing to the warning message text in "messages.properties"
076     * file.
077     */
078    public static final String MSG_MISSING_ASTERISK = "javadoc.missing.asterisk";
079
080    @Override
081    public int[] getRequiredJavadocTokens() {
082        return new int[] {
083            JavadocTokenTypes.NEWLINE,
084        };
085    }
086
087    @Override
088    public int[] getAcceptableJavadocTokens() {
089        return getRequiredJavadocTokens();
090    }
091
092    @Override
093    public int[] getDefaultJavadocTokens() {
094        return getRequiredJavadocTokens();
095    }
096
097    @Override
098    public void visitJavadocToken(DetailNode detailNode) {
099        DetailNode nextSibling = getNextNode(detailNode);
100
101        // Till https://github.com/checkstyle/checkstyle/issues/9005
102        // Due to bug in the Javadoc parser there may be phantom description nodes.
103        while (TokenUtil.isOfType(nextSibling.getType(),
104                JavadocTokenTypes.DESCRIPTION, JavadocTokenTypes.WS)) {
105            nextSibling = getNextNode(nextSibling);
106        }
107
108        if (!isLeadingAsterisk(nextSibling) && !isLastLine(nextSibling)) {
109            log(nextSibling.getLineNumber(), MSG_MISSING_ASTERISK);
110        }
111    }
112
113    /**
114     * Gets next node in the ast (sibling or parent sibling for the last node).
115     *
116     * @param detailNode the node to process
117     * @return next node.
118     */
119    private static DetailNode getNextNode(DetailNode detailNode) {
120        DetailNode node = JavadocUtil.getFirstChild(detailNode);
121        if (node == null) {
122            node = JavadocUtil.getNextSibling(detailNode);
123            if (node == null) {
124                DetailNode parent = detailNode;
125                do {
126                    parent = parent.getParent();
127                    node = JavadocUtil.getNextSibling(parent);
128                } while (node == null);
129            }
130        }
131        return node;
132    }
133
134    /**
135     * Checks whether the given node is a leading asterisk.
136     *
137     * @param detailNode the node to process
138     * @return {@code true} if the node is {@link JavadocTokenTypes#LEADING_ASTERISK}
139     */
140    private static boolean isLeadingAsterisk(DetailNode detailNode) {
141        return detailNode.getType() == JavadocTokenTypes.LEADING_ASTERISK;
142    }
143
144    /**
145     * Checks whether this node is the end of a Javadoc comment,
146     * optionally preceded by blank text.
147     *
148     * @param detailNode the node to process
149     * @return {@code true} if the node is {@link JavadocTokenTypes#EOF}
150     */
151    private static boolean isLastLine(DetailNode detailNode) {
152        final DetailNode node;
153        if (CommonUtil.isBlank(detailNode.getText())) {
154            node = getNextNode(detailNode);
155        }
156        else {
157            node = detailNode;
158        }
159        return node.getType() == JavadocTokenTypes.EOF;
160    }
161
162}