View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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  public class JavadocBlockTagLocationCheck extends AbstractJavadocCheck {
67  
68      /**
69       * A key is pointing to the warning message text in "messages.properties" file.
70       */
71      public static final String MSG_BLOCK_TAG_LOCATION = "javadoc.blockTagLocation";
72  
73      /**
74       * This regexp is used to extract the javadoc tags.
75       */
76      private static final Pattern JAVADOC_BLOCK_TAG_PATTERN = Pattern.compile("\\s@(\\w+)");
77  
78      /**
79       * Block tags from Java 11
80       * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html">
81       * Documentation Comment Specification</a>.
82       */
83      private static final String[] DEFAULT_TAGS = {
84          "author",
85          "deprecated",
86          "exception",
87          "hidden",
88          "param",
89          "provides",
90          "return",
91          "see",
92          "serial",
93          "serialData",
94          "serialField",
95          "since",
96          "throws",
97          "uses",
98          "version",
99      };
100 
101     /**
102      * Specify the javadoc tags to process.
103      */
104     private Set<String> tags;
105 
106     /**
107      * Creates a new {@code JavadocBlockTagLocationCheck} instance with default settings.
108      */
109     public JavadocBlockTagLocationCheck() {
110         setTags(DEFAULT_TAGS);
111     }
112 
113     /**
114      * Setter to specify the javadoc tags to process.
115      *
116      * @param values user's values.
117      * @since 8.24
118      */
119     public final void setTags(String... values) {
120         tags = Arrays.stream(values).collect(Collectors.toUnmodifiableSet());
121     }
122 
123     /**
124      * The javadoc tokens that this check must be registered for. According to
125      * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html#block-tags">
126      * the specs</a> each block tag must appear at the beginning of a line, otherwise
127      * it will be interpreted as a plain text. This check looks for a block tag
128      * in the javadoc text, thus it needs the {@code TEXT} tokens.
129      *
130      * @return the javadoc token set this must be registered for.
131      * @see JavadocCommentsTokenTypes
132      */
133     @Override
134     public int[] getRequiredJavadocTokens() {
135         return new int[] {
136             JavadocCommentsTokenTypes.TEXT,
137         };
138     }
139 
140     @Override
141     public int[] getAcceptableJavadocTokens() {
142         return getRequiredJavadocTokens();
143     }
144 
145     @Override
146     public int[] getDefaultJavadocTokens() {
147         return getRequiredJavadocTokens();
148     }
149 
150     @Override
151     public void visitJavadocToken(DetailNode ast) {
152         if (!isCommentOrInlineTag(ast)) {
153             final Matcher tagMatcher = JAVADOC_BLOCK_TAG_PATTERN.matcher(ast.getText());
154             while (tagMatcher.find()) {
155                 final String tagName = tagMatcher.group(1);
156                 if (tags.contains(tagName)) {
157                     log(ast.getLineNumber(), MSG_BLOCK_TAG_LOCATION, tagName);
158                 }
159             }
160         }
161     }
162 
163     /**
164      * Checks if the node can contain an unescaped block tag without violation.
165      *
166      * @param node to check
167      * @return {@code true} if node is {@code @code}, {@code @literal} or HTML comment.
168      */
169     private static boolean isCommentOrInlineTag(DetailNode node) {
170         boolean isInsideInlineTagOrHtmlComment = false;
171         DetailNode current = node;
172 
173         while (current != null) {
174             if (current.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG
175                     || current.getType() == JavadocCommentsTokenTypes.HTML_COMMENT) {
176                 isInsideInlineTagOrHtmlComment = true;
177                 break;
178             }
179             current = current.getParent();
180         }
181 
182         return isInsideInlineTagOrHtmlComment;
183     }
184 
185 }