001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import java.util.Arrays; 023import java.util.Set; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026import java.util.stream.Collectors; 027 028import com.puppycrawl.tools.checkstyle.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailNode; 030import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 031 032/** 033 * <p> 034 * Checks that a 035 * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#block-tags"> 036 * javadoc block tag</a> appears only at the beginning of a line, ignoring 037 * leading asterisks and white space. A block tag is a token that starts with 038 * {@code @} symbol and is preceded by a whitespace. This check ignores block 039 * tags in comments and inside inline tags {@code } and {@literal }. 040 * </p> 041 * <p> 042 * Rationale: according to 043 * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#block-tags"> 044 * the specification</a> all javadoc block tags should be placed at the beginning 045 * of a line. Tags that are not placed at the beginning are treated as plain text. 046 * To recognize intentional tag placement to text area it is better to escape the 047 * {@code @} symbol, and all non-escaped tags should be located at the beginning 048 * of the line. See NOTE section for details on how to escape. 049 * </p> 050 * <p> 051 * To place a tag explicitly as text, escape the {@code @} symbol with HTML entity 052 * &#64; or place it inside {@code {@code }}, for example: 053 * </p> 054 * <pre> 055 * /** 056 * * &#64;serial literal in {@code @serial} Javadoc tag. 057 * */ 058 * </pre> 059 * <ul> 060 * <li> 061 * Property {@code tags} - Specify the javadoc tags to process. 062 * Type is {@code java.lang.String[]}. 063 * Default value is {@code author, deprecated, exception, hidden, param, provides, 064 * return, see, serial, serialData, serialField, since, throws, uses, version}. 065 * </li> 066 * <li> 067 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations 068 * if the Javadoc being examined by this check violates the tight html rules defined at 069 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>. 070 * Type is {@code boolean}. 071 * Default value is {@code false}. 072 * </li> 073 * </ul> 074 * <p> 075 * To configure the default check: 076 * </p> 077 * <pre> 078 * <module name="JavadocBlockTagLocation"/> 079 * </pre> 080 * <p> 081 * Example: 082 * </p> 083 * <pre> 084 * /** 085 * * Escaped tag &#64;version (OK) 086 * * Plain text with {@code @see} (OK) 087 * * A @custom tag (OK) 088 * * <!-- @see commented out (OK) --> 089 * * email@author (OK) 090 * * (@param in parentheses) (OK) 091 * * '@param in single quotes' (OK) 092 * * @since 1.0 (OK) 093 * * text @return (violation) 094 * * * @param (violation) 095 * +* @serial (violation) 096 * * @see first (OK) @see second (violation) 097 * */ 098 * public int field; 099 * </pre> 100 * <p> 101 * To configure the check to verify tags from 102 * <a href="https://openjdk.java.net/jeps/8068562">JEP 8068562</a> only: 103 * </p> 104 * <pre> 105 * <module name="JavadocBlockTagLocation"> 106 * <property name="tags" value="apiNote, implSpec, implNote"/> 107 * </module> 108 * </pre> 109 * <p> 110 * To configure the check to verify all default tags and some custom tags in addition: 111 * </p> 112 * <pre> 113 * <module name="JavadocBlockTagLocation"> 114 * <!-- default tags --> 115 * <property name="tags" value="author, deprecated, exception, hidden"/> 116 * <property name="tags" value="param, provides, return, see, serial"/> 117 * <property name="tags" value="serialData, serialField, since, throws"/> 118 * <property name="tags" value="uses, version"/> 119 * <!-- additional tags used in the project --> 120 * <property name="tags" value="noinspection"/> 121 * </module> 122 * </pre> 123 * <p> 124 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 125 * </p> 126 * <p> 127 * Violation Message Keys: 128 * </p> 129 * <ul> 130 * <li> 131 * {@code javadoc.blockTagLocation} 132 * </li> 133 * <li> 134 * {@code javadoc.missed.html.close} 135 * </li> 136 * <li> 137 * {@code javadoc.parse.rule.error} 138 * </li> 139 * <li> 140 * {@code javadoc.wrong.singleton.html.tag} 141 * </li> 142 * </ul> 143 * 144 * @since 8.24 145 */ 146@StatelessCheck 147public class JavadocBlockTagLocationCheck extends AbstractJavadocCheck { 148 149 /** 150 * A key is pointing to the warning message text in "messages.properties" file. 151 */ 152 public static final String MSG_BLOCK_TAG_LOCATION = "javadoc.blockTagLocation"; 153 154 /** 155 * This regexp is used to extract the javadoc tags. 156 */ 157 private static final Pattern JAVADOC_BLOCK_TAG_PATTERN = Pattern.compile("\\s@(\\w+)"); 158 159 /** 160 * Block tags from Java 11 161 * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html"> 162 * Documentation Comment Specification</a>. 163 */ 164 private static final String[] DEFAULT_TAGS = { 165 "author", 166 "deprecated", 167 "exception", 168 "hidden", 169 "param", 170 "provides", 171 "return", 172 "see", 173 "serial", 174 "serialData", 175 "serialField", 176 "since", 177 "throws", 178 "uses", 179 "version", 180 }; 181 182 /** 183 * Specify the javadoc tags to process. 184 */ 185 private Set<String> tags; 186 187 /** 188 * Creates a new {@code JavadocBlockTagLocationCheck} instance with default settings. 189 */ 190 public JavadocBlockTagLocationCheck() { 191 setTags(DEFAULT_TAGS); 192 } 193 194 /** 195 * Setter to specify the javadoc tags to process. 196 * 197 * @param values user's values. 198 */ 199 public final void setTags(String... values) { 200 tags = Arrays.stream(values).collect(Collectors.toSet()); 201 } 202 203 /** 204 * The javadoc tokens that this check must be registered for. According to 205 * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#block-tags"> 206 * the specs</a> each block tag must appear at the beginning of a line, otherwise 207 * it will be interpreted as a plain text. This check looks for a block tag 208 * in the javadoc text, thus it needs the {@code TEXT} tokens. 209 * 210 * @return the javadoc token set this must be registered for. 211 * @see JavadocTokenTypes 212 */ 213 @Override 214 public int[] getRequiredJavadocTokens() { 215 return new int[] { 216 JavadocTokenTypes.TEXT, 217 }; 218 } 219 220 @Override 221 public int[] getAcceptableJavadocTokens() { 222 return getRequiredJavadocTokens(); 223 } 224 225 @Override 226 public int[] getDefaultJavadocTokens() { 227 return getRequiredJavadocTokens(); 228 } 229 230 @Override 231 public void visitJavadocToken(DetailNode ast) { 232 if (!isCommentOrInlineTag(ast.getParent())) { 233 final Matcher tagMatcher = JAVADOC_BLOCK_TAG_PATTERN.matcher(ast.getText()); 234 while (tagMatcher.find()) { 235 final String tagName = tagMatcher.group(1); 236 if (tags.contains(tagName)) { 237 log(ast.getLineNumber(), MSG_BLOCK_TAG_LOCATION, tagName); 238 } 239 } 240 } 241 } 242 243 /** 244 * Checks if the node can contain an unescaped block tag without violation. 245 * 246 * @param node to check 247 * @return {@code true} if node is {@code @code}, {@code @literal} or HTML comment. 248 */ 249 private static boolean isCommentOrInlineTag(DetailNode node) { 250 return node.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG 251 || node.getType() == JavadocTokenTypes.HTML_COMMENT; 252 } 253 254}