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 {@code } and {@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 * &#64; or place it inside {@code {@code }}, for example:
56 * </p>
57 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
58 * /**
59 * * &#64;serial literal in {@code @serial} Javadoc tag.
60 * */
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 }