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