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