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