View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.javadoc;
21  
22  import java.util.Arrays;
23  import java.util.Set;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  import java.util.stream.Collectors;
27  
28  import com.puppycrawl.tools.checkstyle.StatelessCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailNode;
30  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
31  
32  /**
33   * <div>
34   * Checks that a
35   * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html#block-tags">
36   * javadoc block tag</a> appears only at the beginning of a line, ignoring
37   * leading asterisks and white space. A block tag is a token that starts with
38   * {@code @} symbol and is preceded by a whitespace. This check ignores block
39   * tags in comments and inside inline tags {&#64;code } and {&#64;literal }.
40   * </div>
41   *
42   * <p>
43   * Rationale: according to
44   * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html#block-tags">
45   * the specification</a> all javadoc block tags should be placed at the beginning
46   * of a line. Tags that are not placed at the beginning are treated as plain text.
47   * To recognize intentional tag placement to text area it is better to escape the
48   * {@code @} symbol, and all non-escaped tags should be located at the beginning
49   * of the line. See NOTE section for details on how to escape.
50   * </p>
51   *
52   * <p>
53   * Notes:
54   * To place a tag explicitly as text, escape the {@code @} symbol with HTML entity
55   * &amp;#64; or place it inside {@code {@code }}, for example:
56   * </p>
57   * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
58   * &#47;**
59   *  * &amp;#64;serial literal in {&#64;code &#64;serial} Javadoc tag.
60   *  *&#47;
61   * </code></pre></div>
62   *
63   * @since 8.24
64   */
65  @StatelessCheck
66  @SuppressWarnings("UnrecognisedJavadocTag")
67  public class JavadocBlockTagLocationCheck extends AbstractJavadocCheck {
68  
69      /**
70       * A key is pointing to the warning message text in "messages.properties" file.
71       */
72      public static final String MSG_BLOCK_TAG_LOCATION = "javadoc.blockTagLocation";
73  
74      /**
75       * This regexp is used to extract the javadoc tags.
76       */
77      private static final Pattern JAVADOC_BLOCK_TAG_PATTERN = Pattern.compile("\\s@(\\w+)");
78  
79      /**
80       * Block tags from Java 11
81       * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html">
82       * Documentation Comment Specification</a>.
83       */
84      private static final String[] DEFAULT_TAGS = {
85          "author",
86          "deprecated",
87          "exception",
88          "hidden",
89          "param",
90          "provides",
91          "return",
92          "see",
93          "serial",
94          "serialData",
95          "serialField",
96          "since",
97          "throws",
98          "uses",
99          "version",
100     };
101 
102     /**
103      * Specify the javadoc tags to process.
104      */
105     private Set<String> tags;
106 
107     /**
108      * Creates a new {@code JavadocBlockTagLocationCheck} instance with default settings.
109      */
110     public JavadocBlockTagLocationCheck() {
111         setTags(DEFAULT_TAGS);
112     }
113 
114     /**
115      * Setter to specify the javadoc tags to process.
116      *
117      * @param values user's values.
118      * @since 8.24
119      */
120     public final void setTags(String... values) {
121         tags = Arrays.stream(values).collect(Collectors.toUnmodifiableSet());
122     }
123 
124     /**
125      * The javadoc tokens that this check must be registered for. According to
126      * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html#block-tags">
127      * the specs</a> each block tag must appear at the beginning of a line, otherwise
128      * it will be interpreted as a plain text. This check looks for a block tag
129      * in the javadoc text, thus it needs the {@code TEXT} tokens.
130      *
131      * @return the javadoc token set this must be registered for.
132      * @see JavadocCommentsTokenTypes
133      */
134     @Override
135     public int[] getRequiredJavadocTokens() {
136         return new int[] {
137             JavadocCommentsTokenTypes.TEXT,
138         };
139     }
140 
141     @Override
142     public int[] getAcceptableJavadocTokens() {
143         return getRequiredJavadocTokens();
144     }
145 
146     @Override
147     public int[] getDefaultJavadocTokens() {
148         return getRequiredJavadocTokens();
149     }
150 
151     @Override
152     public void visitJavadocToken(DetailNode ast) {
153         if (!isCommentOrInlineTag(ast)) {
154             final Matcher tagMatcher = JAVADOC_BLOCK_TAG_PATTERN.matcher(ast.getText());
155             while (tagMatcher.find()) {
156                 final String tagName = tagMatcher.group(1);
157                 if (tags.contains(tagName)) {
158                     log(ast.getLineNumber(), MSG_BLOCK_TAG_LOCATION, tagName);
159                 }
160             }
161         }
162     }
163 
164     /**
165      * Checks if the node can contain an unescaped block tag without violation.
166      *
167      * @param node to check
168      * @return {@code true} if node is {@code @code}, {@code @literal} or HTML comment.
169      */
170     private static boolean isCommentOrInlineTag(DetailNode node) {
171         boolean isInsideInlineTagOrHtmlComment = false;
172         DetailNode current = node;
173 
174         while (current != null) {
175             if (current.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG
176                     || current.getType() == JavadocCommentsTokenTypes.HTML_COMMENT) {
177                 isInsideInlineTagOrHtmlComment = true;
178                 break;
179             }
180             current = current.getParent();
181         }
182 
183         return isInsideInlineTagOrHtmlComment;
184     }
185 
186 }