001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.Set;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.DetailNode;
027import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
029import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
030
031/**
032 * <div>
033 * Checks that a Javadoc block can fit in a single-line and doesn't contain block tags.
034 * Javadoc comment that contains at least one block tag should be formatted in a few lines.
035 * </div>
036 *
037 * @since 6.0
038 */
039@StatelessCheck
040public class SingleLineJavadocCheck extends AbstractJavadocCheck {
041
042    /**
043     * A key is pointing to the warning message text in "messages.properties"
044     * file.
045     */
046    public static final String MSG_KEY = "singleline.javadoc";
047
048    /**
049     * Specify
050     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
051     * block tags</a> which are ignored by the check.
052     */
053    private Set<String> ignoredTags = Set.of();
054
055    /**
056     * Control whether
057     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
058     * inline tags</a> must be ignored.
059     */
060    private boolean ignoreInlineTags = true;
061
062    /**
063     * Creates a new {@code SingleLineJavadocCheck} instance.
064     */
065    public SingleLineJavadocCheck() {
066        // no code by default
067    }
068
069    /**
070     * Setter to specify
071     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
072     * block tags</a> which are ignored by the check.
073     *
074     * @param tags to be ignored by check.
075     * @since 6.8
076     */
077    public void setIgnoredTags(String... tags) {
078        ignoredTags = Set.of(tags);
079    }
080
081    /**
082     * Setter to control whether
083     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
084     * inline tags</a> must be ignored.
085     *
086     * @param ignoreInlineTags whether inline tags must be ignored.
087     * @since 6.8
088     */
089    public void setIgnoreInlineTags(boolean ignoreInlineTags) {
090        this.ignoreInlineTags = ignoreInlineTags;
091    }
092
093    @Override
094    public int[] getDefaultJavadocTokens() {
095        return new int[] {
096            JavadocCommentsTokenTypes.JAVADOC_CONTENT,
097        };
098    }
099
100    @Override
101    public int[] getRequiredJavadocTokens() {
102        return getAcceptableJavadocTokens();
103    }
104
105    @Override
106    public void visitJavadocToken(DetailNode ast) {
107        if (isSingleLineJavadoc(getBlockCommentAst())
108                && (hasJavadocTags(ast) || !ignoreInlineTags && hasJavadocInlineTags(ast))) {
109            log(ast.getLineNumber(), MSG_KEY);
110        }
111    }
112
113    /**
114     * Checks if comment is single-line comment.
115     *
116     * @param blockCommentStart the AST tree in which a block comment starts
117     * @return true, if comment is single-line comment.
118     */
119    private static boolean isSingleLineJavadoc(DetailAST blockCommentStart) {
120        final DetailAST blockCommentEnd = blockCommentStart.getLastChild();
121        return TokenUtil.areOnSameLine(blockCommentStart, blockCommentEnd);
122    }
123
124    /**
125     * Checks if comment has javadoc tags which are not ignored. Also works
126     * on custom tags. As block tags can be interpreted only at the beginning of a line,
127     * only the first instance is checked.
128     *
129     * @param javadocRoot javadoc root node.
130     * @return true, if comment has javadoc tags which are not ignored.
131     * @see <a href=
132     *     "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#blockandinlinetags">
133     *     Block and inline tags</a>
134     */
135    private boolean hasJavadocTags(DetailNode javadocRoot) {
136        final DetailNode javadocTagSection =
137                JavadocUtil.findFirstToken(
138                        javadocRoot, JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG);
139        return javadocTagSection != null && !isTagIgnored(javadocTagSection);
140    }
141
142    /**
143     * Checks if comment has in-line tags which are not ignored.
144     *
145     * @param javadocRoot javadoc root node.
146     * @return true, if comment has in-line tags which are not ignored.
147     * @see <a href=
148     *     "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags">
149     *     JavadocTags</a>
150     */
151    private boolean hasJavadocInlineTags(DetailNode javadocRoot) {
152        DetailNode javadocTagSection =
153                JavadocUtil.findFirstToken(
154                        javadocRoot, JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG);
155        boolean foundTag = false;
156        while (javadocTagSection != null) {
157            if (!isTagIgnored(javadocTagSection)) {
158                foundTag = true;
159                break;
160            }
161            javadocTagSection = JavadocUtil.getNextSibling(
162                    javadocTagSection, JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG);
163        }
164        return foundTag;
165    }
166
167    /**
168     * Checks if list of ignored tags contains javadocTagSection's javadoc tag.
169     *
170     * @param javadocTagSection to check javadoc tag in.
171     * @return true, if ignoredTags contains javadocTagSection's javadoc tag.
172     */
173    private boolean isTagIgnored(DetailNode javadocTagSection) {
174        final String tagName = JavadocUtil.getTagName(javadocTagSection);
175        return ignoredTags.contains("@" + tagName);
176    }
177
178}