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 * <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.unclosedHtml}
066 * </li>
067 * <li>
068 * {@code javadoc.wrong.singleton.html.tag}
069 * </li>
070 * </ul>
071 *
072 * @since 8.38
073 */
074@StatelessCheck
075public class JavadocMissingLeadingAsteriskCheck extends AbstractJavadocCheck {
076
077    /**
078     * A key is pointing to the warning message text in "messages.properties"
079     * file.
080     */
081    public static final String MSG_MISSING_ASTERISK = "javadoc.missing.asterisk";
082
083    @Override
084    public int[] getRequiredJavadocTokens() {
085        return new int[] {
086            JavadocTokenTypes.NEWLINE,
087        };
088    }
089
090    @Override
091    public int[] getAcceptableJavadocTokens() {
092        return getRequiredJavadocTokens();
093    }
094
095    @Override
096    public int[] getDefaultJavadocTokens() {
097        return getRequiredJavadocTokens();
098    }
099
100    @Override
101    public void visitJavadocToken(DetailNode detailNode) {
102        DetailNode nextSibling = getNextNode(detailNode);
103
104        // Till https://github.com/checkstyle/checkstyle/issues/9005
105        // Due to bug in the Javadoc parser there may be phantom description nodes.
106        while (TokenUtil.isOfType(nextSibling.getType(),
107                JavadocTokenTypes.DESCRIPTION, JavadocTokenTypes.WS)) {
108            nextSibling = getNextNode(nextSibling);
109        }
110
111        if (!isLeadingAsterisk(nextSibling) && !isLastLine(nextSibling)) {
112            log(nextSibling.getLineNumber(), MSG_MISSING_ASTERISK);
113        }
114    }
115
116    /**
117     * Gets next node in the ast (sibling or parent sibling for the last node).
118     *
119     * @param detailNode the node to process
120     * @return next node.
121     */
122    private static DetailNode getNextNode(DetailNode detailNode) {
123        DetailNode node = JavadocUtil.getFirstChild(detailNode);
124        if (node == null) {
125            node = JavadocUtil.getNextSibling(detailNode);
126            if (node == null) {
127                DetailNode parent = detailNode;
128                do {
129                    parent = parent.getParent();
130                    node = JavadocUtil.getNextSibling(parent);
131                } while (node == null);
132            }
133        }
134        return node;
135    }
136
137    /**
138     * Checks whether the given node is a leading asterisk.
139     *
140     * @param detailNode the node to process
141     * @return {@code true} if the node is {@link JavadocTokenTypes#LEADING_ASTERISK}
142     */
143    private static boolean isLeadingAsterisk(DetailNode detailNode) {
144        return detailNode.getType() == JavadocTokenTypes.LEADING_ASTERISK;
145    }
146
147    /**
148     * Checks whether this node is the end of a Javadoc comment,
149     * optionally preceded by blank text.
150     *
151     * @param detailNode the node to process
152     * @return {@code true} if the node is {@link JavadocTokenTypes#EOF}
153     */
154    private static boolean isLastLine(DetailNode detailNode) {
155        final DetailNode node;
156        if (CommonUtil.isBlank(detailNode.getText())) {
157            node = getNextNode(detailNode);
158        }
159        else {
160            node = detailNode;
161        }
162        return node.getType() == JavadocTokenTypes.EOF;
163    }
164
165}