001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.JavadocTokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
029import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
030
031/**
032 * <p>
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 * </p>
036 * <ul>
037 * <li>
038 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
039 * if the Javadoc being examined by this check violates the tight html rules defined at
040 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
041 * Type is {@code boolean}.
042 * Default value is {@code false}.
043 * </li>
044 * <li>
045 * Property {@code ignoredTags} - Specify
046 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
047 * block tags</a> which are ignored by the check.
048 * Type is {@code java.lang.String[]}.
049 * Default value is {@code ""}.
050 * </li>
051 * <li>
052 * Property {@code ignoreInlineTags} - Control whether
053 * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
054 * inline tags</a> must be ignored.
055 * Type is {@code boolean}.
056 * Default value is {@code true}.
057 * </li>
058 * </ul>
059 * <p>
060 * To configure the check:
061 * </p>
062 * <pre>
063 * &lt;module name=&quot;SingleLineJavadoc&quot;/&gt;
064 * </pre>
065 * <p>Example:</p>
066 * <pre>
067 * &#47;** &#64;see Math *&#47; // violation, javadoc should be multiline
068 * public int foo() {
069 *   return 42;
070 * }
071 *
072 * &#47;**
073 *  * &#64;return 42
074 *  *&#47;  // ok
075 * public int bar() {
076 *   return 42;
077 * }
078 *
079 * &#47;** {&#64;link #equals(Object)} *&#47; // ok
080 * public int baz() {
081 *   return 42;
082 * }
083 *
084 * &#47;**
085 *  * &lt;p&gt;the answer to the ultimate question
086 *  *&#47; // ok
087 * public int magic() {
088 *   return 42;
089 * }
090 *
091 * &#47;**
092 *  * &lt;p&gt;the answer to the ultimate question&lt;/p&gt;
093 *  *&#47; // ok
094 * public int foobar() {
095 *   return 42;
096 * }
097 * </pre>
098 * <p>
099 * To configure the check with a list of ignored block tags:
100 * </p>
101 * <pre>
102 * &lt;module name=&quot;SingleLineJavadoc&quot;&gt;
103 *   &lt;property name=&quot;ignoredTags&quot; value=&quot;&#64;inheritDoc, &#64;see&quot;/&gt;
104 * &lt;/module&gt;
105 * </pre>
106 * <p>Example:</p>
107 * <pre>
108 * &#47;** &#64;see Math *&#47; // ok
109 * public int foo() {
110 *   return 42;
111 * }
112 *
113 * &#47;**
114 *  * &#64;return 42
115 *  *&#47;  // ok
116 * public int bar() {
117 *   return 42;
118 * }
119 *
120 * &#47;** {&#64;link #equals(Object)} *&#47; // ok
121 * public int baz() {
122 *   return 42;
123 * }
124 *
125 * &#47;**
126 *  * &lt;p&gt;the answer to the ultimate question
127 *  *&#47; // ok
128 * public int magic() {
129 *   return 42;
130 * }
131 *
132 * &#47;**
133 *  * &lt;p&gt;the answer to the ultimate question&lt;/p&gt;
134 *  *&#47; // ok
135 * public int foobar() {
136 *   return 42;
137 * }
138 * </pre>
139 * <p>
140 * To configure the check to not ignore inline tags:
141 * </p>
142 * <pre>
143 * &lt;module name=&quot;SingleLineJavadoc&quot;&gt;
144 *   &lt;property name=&quot;ignoreInlineTags&quot; value=&quot;false&quot;/&gt;
145 * &lt;/module&gt;
146 * </pre>
147 * <p>Example:</p>
148 * <pre>
149 * &#47;** &#64;see Math *&#47; // violation, javadoc should be multiline
150 * public int foo() {
151 *   return 42;
152 * }
153 *
154 * &#47;**
155 *  * &#64;return 42
156 *  *&#47;  // ok
157 * public int bar() {
158 *   return 42;
159 * }
160 *
161 * &#47;** {&#64;link #equals(Object)} *&#47; // violation, javadoc should be multiline
162 * public int baz() {
163 *   return 42;
164 * }
165 *
166 * &#47;**
167 *  * &lt;p&gt;the answer to the ultimate question
168 *  *&#47; // ok
169 * public int magic() {
170 *   return 42;
171 * }
172 *
173 * &#47;**
174 *  * &lt;p&gt;the answer to the ultimate question&lt;/p&gt;
175 *  *&#47; // ok
176 * public int foobar() {
177 *   return 42;
178 * }
179 * </pre>
180 * <p>
181 * To configure the check to report violations for Tight-HTML Rules:
182 * </p>
183 * <pre>
184 * &lt;module name=&quot;SingleLineJavadoc&quot;&gt;
185 *   &lt;property name=&quot;violateExecutionOnNonTightHtml&quot; value=&quot;true&quot;/&gt;
186 * &lt;/module&gt;
187 * </pre>
188 * <p>Example:</p>
189 * <pre>
190 * &#47;** &#64;see Math *&#47; // violation, javadoc should be multiline
191 * public int foo() {
192 *   return 42;
193 * }
194 *
195 * &#47;**
196 *  * &#64;return 42
197 *  *&#47;  // ok
198 * public int bar() {
199 *   return 42;
200 * }
201 *
202 * &#47;** {&#64;link #equals(Object)} *&#47; // ok
203 * public int baz() {
204 *   return 42;
205 * }
206 *
207 * &#47;**
208 *  * &lt;p&gt;the answer to the ultimate question
209 *  *&#47; // violation, unclosed HTML tag found: p
210 * public int magic() {
211 *   return 42;
212 * }
213 *
214 * &#47;**
215 *  * &lt;p&gt;the answer to the ultimate question&lt;/p&gt;
216 *  *&#47; // ok
217 * public int foobar() {
218 *   return 42;
219 * }
220 * </pre>
221 * <p>
222 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
223 * </p>
224 * <p>
225 * Violation Message Keys:
226 * </p>
227 * <ul>
228 * <li>
229 * {@code javadoc.missed.html.close}
230 * </li>
231 * <li>
232 * {@code javadoc.parse.rule.error}
233 * </li>
234 * <li>
235 * {@code javadoc.wrong.singleton.html.tag}
236 * </li>
237 * <li>
238 * {@code singleline.javadoc}
239 * </li>
240 * </ul>
241 *
242 * @since 6.0
243 */
244@StatelessCheck
245public class SingleLineJavadocCheck extends AbstractJavadocCheck {
246
247    /**
248     * A key is pointing to the warning message text in "messages.properties"
249     * file.
250     */
251    public static final String MSG_KEY = "singleline.javadoc";
252
253    /**
254     * Specify
255     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
256     * block tags</a> which are ignored by the check.
257     */
258    private Set<String> ignoredTags = Set.of();
259
260    /**
261     * Control whether
262     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
263     * inline tags</a> must be ignored.
264     */
265    private boolean ignoreInlineTags = true;
266
267    /**
268     * Setter to specify
269     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
270     * block tags</a> which are ignored by the check.
271     *
272     * @param tags to be ignored by check.
273     */
274    public void setIgnoredTags(String... tags) {
275        ignoredTags = Set.of(tags);
276    }
277
278    /**
279     * Setter to control whether
280     * <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html#CHDBEFIF">
281     * inline tags</a> must be ignored.
282     *
283     * @param ignoreInlineTags whether inline tags must be ignored.
284     */
285    public void setIgnoreInlineTags(boolean ignoreInlineTags) {
286        this.ignoreInlineTags = ignoreInlineTags;
287    }
288
289    @Override
290    public int[] getDefaultJavadocTokens() {
291        return new int[] {
292            JavadocTokenTypes.JAVADOC,
293        };
294    }
295
296    @Override
297    public int[] getRequiredJavadocTokens() {
298        return getAcceptableJavadocTokens();
299    }
300
301    @Override
302    public void visitJavadocToken(DetailNode ast) {
303        if (isSingleLineJavadoc(getBlockCommentAst())
304                && (hasJavadocTags(ast) || !ignoreInlineTags && hasJavadocInlineTags(ast))) {
305            log(ast.getLineNumber(), MSG_KEY);
306        }
307    }
308
309    /**
310     * Checks if comment is single-line comment.
311     *
312     * @param blockCommentStart the AST tree in which a block comment starts
313     * @return true, if comment is single-line comment.
314     */
315    private static boolean isSingleLineJavadoc(DetailAST blockCommentStart) {
316        final DetailAST blockCommentEnd = blockCommentStart.getLastChild();
317        return TokenUtil.areOnSameLine(blockCommentStart, blockCommentEnd);
318    }
319
320    /**
321     * Checks if comment has javadoc tags which are not ignored. Also works
322     * on custom tags. As block tags can be interpreted only at the beginning of a line,
323     * only the first instance is checked.
324     *
325     * @param javadocRoot javadoc root node.
326     * @return true, if comment has javadoc tags which are not ignored.
327     * @see <a href=
328     * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#blockandinlinetags">
329     * Block and inline tags</a>
330     */
331    private boolean hasJavadocTags(DetailNode javadocRoot) {
332        final DetailNode javadocTagSection =
333                JavadocUtil.findFirstToken(javadocRoot, JavadocTokenTypes.JAVADOC_TAG);
334        return javadocTagSection != null && !isTagIgnored(javadocTagSection);
335    }
336
337    /**
338     * Checks if comment has in-line tags which are not ignored.
339     *
340     * @param javadocRoot javadoc root node.
341     * @return true, if comment has in-line tags which are not ignored.
342     * @see <a href=
343     * "https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadoctags">
344     * JavadocTags</a>
345     */
346    private boolean hasJavadocInlineTags(DetailNode javadocRoot) {
347        DetailNode javadocTagSection =
348                JavadocUtil.findFirstToken(javadocRoot, JavadocTokenTypes.JAVADOC_INLINE_TAG);
349        boolean foundTag = false;
350        while (javadocTagSection != null) {
351            if (!isTagIgnored(javadocTagSection)) {
352                foundTag = true;
353                break;
354            }
355            javadocTagSection = JavadocUtil.getNextSibling(
356                    javadocTagSection, JavadocTokenTypes.JAVADOC_INLINE_TAG);
357        }
358        return foundTag;
359    }
360
361    /**
362     * Checks if list of ignored tags contains javadocTagSection's javadoc tag.
363     *
364     * @param javadocTagSection to check javadoc tag in.
365     * @return true, if ignoredTags contains javadocTagSection's javadoc tag.
366     */
367    private boolean isTagIgnored(DetailNode javadocTagSection) {
368        return ignoredTags.contains(JavadocUtil.getTagName(javadocTagSection));
369    }
370
371}