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 java.util.ArrayList;
023import java.util.Arrays;
024import java.util.List;
025import java.util.stream.Collectors;
026
027import com.puppycrawl.tools.checkstyle.StatelessCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
032
033/**
034 * <p>
035 * Checks that a Javadoc block can fit in a single line and doesn't contain at-clauses.
036 * Javadoc comment that contains at least one at-clause should be formatted in a few lines.
037 * </p>
038 * <ul>
039 * <li>
040 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
041 * if the Javadoc being examined by this check violates the tight html rules defined at
042 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
043 * Default value is {@code false}.
044 * </li>
045 * <li>
046 * Property {@code ignoredTags} - Specify at-clauses which are ignored by the check.
047 * Default value is {@code {}}.
048 * </li>
049 * <li>
050 * Property {@code ignoreInlineTags} - Control whether inline tags must be ignored.
051 * Default value is {@code true}.
052 * </li>
053 * </ul>
054 * <p>
055 * To configure the check:
056 * </p>
057 * <pre>
058 * &lt;module name=&quot;SingleLineJavadoc&quot;/&gt;
059 * </pre>
060 * <p>
061 * To configure the check with a list of ignored at-clauses
062 * and make inline at-clauses not ignored:
063 * </p>
064 * <pre>
065 * &lt;module name=&quot;SingleLineJavadoc&quot;&gt;
066 *   &lt;property name=&quot;ignoredTags&quot; value=&quot;&#64;inheritDoc, &#64;see&quot;/&gt;
067 *   &lt;property name=&quot;ignoreInlineTags&quot; value=&quot;false&quot;/&gt;
068 * &lt;/module&gt;
069 * </pre>
070 *
071 * @since 6.0
072 */
073@StatelessCheck
074public class SingleLineJavadocCheck extends AbstractJavadocCheck {
075
076    /**
077     * A key is pointing to the warning message text in "messages.properties"
078     * file.
079     */
080    public static final String MSG_KEY = "singleline.javadoc";
081
082    /**
083     * Specify at-clauses which are ignored by the check.
084     */
085    private List<String> ignoredTags = new ArrayList<>();
086
087    /** Control whether inline tags must be ignored. */
088    private boolean ignoreInlineTags = true;
089
090    /**
091     * Setter to specify at-clauses which are ignored by the check.
092     *
093     * @param tags to be ignored by check.
094     */
095    public void setIgnoredTags(String... tags) {
096        ignoredTags = Arrays.stream(tags).collect(Collectors.toList());
097    }
098
099    /**
100     * Setter to control whether inline tags must be ignored.
101     *
102     * @param ignoreInlineTags whether inline tags must be ignored.
103     */
104    public void setIgnoreInlineTags(boolean ignoreInlineTags) {
105        this.ignoreInlineTags = ignoreInlineTags;
106    }
107
108    @Override
109    public int[] getDefaultJavadocTokens() {
110        return new int[] {
111            JavadocTokenTypes.JAVADOC,
112        };
113    }
114
115    @Override
116    public int[] getRequiredJavadocTokens() {
117        return getAcceptableJavadocTokens();
118    }
119
120    @Override
121    public void visitJavadocToken(DetailNode ast) {
122        if (isSingleLineJavadoc(getBlockCommentAst())
123                && (hasJavadocTags(ast) || !ignoreInlineTags && hasJavadocInlineTags(ast))) {
124            log(ast.getLineNumber(), MSG_KEY);
125        }
126    }
127
128    /**
129     * Checks if comment is single line comment.
130     *
131     * @param blockCommentStart the AST tree in which a block comment starts
132     * @return true, if comment is single line comment.
133     */
134    private static boolean isSingleLineJavadoc(DetailAST blockCommentStart) {
135        final DetailAST blockCommentEnd = blockCommentStart.getLastChild();
136        return blockCommentStart.getLineNo() == blockCommentEnd.getLineNo();
137    }
138
139    /**
140     * Checks if comment has javadoc tags which are not ignored. Also works
141     * on custom tags. As block tags can be interpreted only at the beginning of a line,
142     * only the first instance is checked.
143     *
144     * @param javadocRoot javadoc root node.
145     * @return true, if comment has javadoc tags which are not ignored.
146     * @see <a href=
147     * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#blockandinlinetags">
148     * Block and inline tags</a>
149     */
150    private boolean hasJavadocTags(DetailNode javadocRoot) {
151        final DetailNode javadocTagSection =
152                JavadocUtil.findFirstToken(javadocRoot, JavadocTokenTypes.JAVADOC_TAG);
153        return javadocTagSection != null && !isTagIgnored(javadocTagSection);
154    }
155
156    /**
157     * Checks if comment has in-line tags which are not ignored.
158     *
159     * @param javadocRoot javadoc root node.
160     * @return true, if comment has in-line tags which are not ignored.
161     * @see <a href=
162     * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags">
163     * JavadocTags</a>
164     */
165    private boolean hasJavadocInlineTags(DetailNode javadocRoot) {
166        DetailNode javadocTagSection =
167                JavadocUtil.findFirstToken(javadocRoot, JavadocTokenTypes.JAVADOC_INLINE_TAG);
168        boolean foundTag = false;
169        while (javadocTagSection != null) {
170            if (!isTagIgnored(javadocTagSection)) {
171                foundTag = true;
172                break;
173            }
174            javadocTagSection = JavadocUtil.getNextSibling(
175                    javadocTagSection, JavadocTokenTypes.JAVADOC_INLINE_TAG);
176        }
177        return foundTag;
178    }
179
180    /**
181     * Checks if list of ignored tags contains javadocTagSection's javadoc tag.
182     *
183     * @param javadocTagSection to check javadoc tag in.
184     * @return true, if ignoredTags contains javadocTagSection's javadoc tag.
185     */
186    private boolean isTagIgnored(DetailNode javadocTagSection) {
187        return ignoredTags.contains(JavadocUtil.getTagName(javadocTagSection));
188    }
189
190}