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 * To configure the check:
050 * </p>
051 * <pre>
052 * &lt;module name="JavadocMissingLeadingAsterisk"/&gt;
053 * </pre>
054 * <p>
055 * Example:
056 * </p>
057 * <pre>
058 * &#47;**
059 *  * Valid Java-style comment.
060 *  *
061 *  * &lt;pre&gt;
062 *  *   int value = 0;
063 *  * &lt;/pre&gt;
064 *  *&#47;
065 * class JavaStyle {} // ok
066 *
067 * &#47;** Valid Scala-style comment.
068 *   * Some description here.
069 *   **&#47;
070 * class ScalaStyle {} // ok
071 *
072 * &#47;** **
073 *  * Asterisks on first and last lines are optional.
074 *  * *&#47;
075 * class Asterisks {} // ok
076 *
077 * &#47;** No asterisks are required for single-line comments. *&#47;
078 * class SingleLine {} // ok
079 *
080 * &#47;** // violation on next blank line, javadoc has lines without leading asterisk.
081 *
082 *  *&#47;
083 * class BlankLine {}
084 *
085 * &#47;** Wrapped
086 *     single-line comment *&#47; // violation, javadoc has lines without leading asterisk.
087 * class Wrapped {}
088 *
089 * &#47;**
090 *  * &lt;pre&gt;
091 *     int value; // violation, javadoc has lines without leading asterisk.
092 *  * &lt;/pre&gt;
093 *  *&#47;
094 * class Code {}
095 * </pre>
096 * <p>
097 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
098 * </p>
099 * <p>
100 * Violation Message Keys:
101 * </p>
102 * <ul>
103 * <li>
104 * {@code javadoc.missed.html.close}
105 * </li>
106 * <li>
107 * {@code javadoc.missing.asterisk}
108 * </li>
109 * <li>
110 * {@code javadoc.parse.rule.error}
111 * </li>
112 * <li>
113 * {@code javadoc.wrong.singleton.html.tag}
114 * </li>
115 * </ul>
116 *
117 * @since 8.38
118 */
119@StatelessCheck
120public class JavadocMissingLeadingAsteriskCheck extends AbstractJavadocCheck {
121
122    /**
123     * A key is pointing to the warning message text in "messages.properties"
124     * file.
125     */
126    public static final String MSG_MISSING_ASTERISK = "javadoc.missing.asterisk";
127
128    @Override
129    public int[] getRequiredJavadocTokens() {
130        return new int[] {
131            JavadocTokenTypes.NEWLINE,
132        };
133    }
134
135    @Override
136    public int[] getAcceptableJavadocTokens() {
137        return getRequiredJavadocTokens();
138    }
139
140    @Override
141    public int[] getDefaultJavadocTokens() {
142        return getRequiredJavadocTokens();
143    }
144
145    @Override
146    public void visitJavadocToken(DetailNode detailNode) {
147        DetailNode nextSibling = getNextNode(detailNode);
148
149        // Till https://github.com/checkstyle/checkstyle/issues/9005
150        // Due to bug in the Javadoc parser there may be phantom description nodes.
151        while (TokenUtil.isOfType(nextSibling.getType(),
152                JavadocTokenTypes.DESCRIPTION, JavadocTokenTypes.WS)) {
153            nextSibling = getNextNode(nextSibling);
154        }
155
156        if (!isLeadingAsterisk(nextSibling) && !isLastLine(nextSibling)) {
157            log(nextSibling.getLineNumber(), MSG_MISSING_ASTERISK);
158        }
159    }
160
161    /**
162     * Gets next node in the ast (sibling or parent sibling for the last node).
163     *
164     * @param detailNode the node to process
165     * @return next node.
166     */
167    private static DetailNode getNextNode(DetailNode detailNode) {
168        DetailNode node = JavadocUtil.getFirstChild(detailNode);
169        if (node == null) {
170            node = JavadocUtil.getNextSibling(detailNode);
171            if (node == null) {
172                DetailNode parent = detailNode;
173                do {
174                    parent = parent.getParent();
175                    node = JavadocUtil.getNextSibling(parent);
176                } while (node == null);
177            }
178        }
179        return node;
180    }
181
182    /**
183     * Checks whether the given node is a leading asterisk.
184     *
185     * @param detailNode the node to process
186     * @return {@code true} if the node is {@link JavadocTokenTypes#LEADING_ASTERISK}
187     */
188    private static boolean isLeadingAsterisk(DetailNode detailNode) {
189        return detailNode.getType() == JavadocTokenTypes.LEADING_ASTERISK;
190    }
191
192    /**
193     * Checks whether this node is the end of a Javadoc comment,
194     * optionally preceded by blank text.
195     *
196     * @param detailNode the node to process
197     * @return {@code true} if the node is {@link JavadocTokenTypes#EOF}
198     */
199    private static boolean isLastLine(DetailNode detailNode) {
200        final DetailNode node;
201        if (detailNode.getType() == JavadocTokenTypes.TEXT
202                && CommonUtil.isBlank(detailNode.getText())) {
203            node = getNextNode(detailNode);
204        }
205        else {
206            node = detailNode;
207        }
208        return node.getType() == JavadocTokenTypes.EOF;
209    }
210
211}