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 }