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 {@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 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 }