001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.regex.Matcher; 023import java.util.regex.Pattern; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.FileContents; 029import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 030import com.puppycrawl.tools.checkstyle.api.TextBlock; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033 034/** 035 * <p> 036 * Requires user defined Javadoc tag to be present in Javadoc comment with defined format. 037 * To define the format for a tag, set property tagFormat to a regular expression. 038 * Property tagSeverity is used for severity of events when the tag exists. 039 * </p> 040 * <ul> 041 * <li> 042 * Property {@code tag} - Specify the name of tag. 043 * Type is {@code java.lang.String}. 044 * Default value is {@code null}. 045 * </li> 046 * <li> 047 * Property {@code tagFormat} - Specify the regexp to match tag content. 048 * Type is {@code java.util.regex.Pattern}. 049 * Default value is {@code null}. 050 * </li> 051 * <li> 052 * Property {@code tagSeverity} - Specify the severity level when tag is found and printed. 053 * Type is {@code com.puppycrawl.tools.checkstyle.api.SeverityLevel}. 054 * Default value is {@code info}. 055 * </li> 056 * <li> 057 * Property {@code tokens} - tokens to check 058 * Type is {@code java.lang.String[]}. 059 * Validation type is {@code tokenSet}. 060 * Default value is: 061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 062 * INTERFACE_DEF</a>, 063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 064 * CLASS_DEF</a>, 065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 066 * ENUM_DEF</a>, 067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 068 * ANNOTATION_DEF</a>, 069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 070 * RECORD_DEF</a>. 071 * </li> 072 * </ul> 073 * <p> 074 * Example of default Check configuration that do nothing. 075 * </p> 076 * <pre> 077 * <module name="WriteTag"/> 078 * </pre> 079 * <p> 080 * Example: 081 * </p> 082 * <pre> 083 * /** 084 * * Some class 085 * */ 086 * public class Test { 087 * /** some doc */ 088 * void foo() {} 089 * } 090 * </pre> 091 * <p> 092 * To configure Check to demand some special tag (for example {@code @since}) 093 * to be present on classes javadoc. 094 * </p> 095 * <pre> 096 * <module name="WriteTag"> 097 * <property name="tag" value="@since"/> 098 * </module> 099 * </pre> 100 * <p> 101 * Example: 102 * </p> 103 * <pre> 104 * /** 105 * * Some class 106 * */ 107 * public class Test { // violation as required tag is missed 108 * /** some doc */ 109 * void foo() {} // OK, as methods are not checked by default 110 * } 111 * </pre> 112 * <p> 113 * To configure Check to demand some special tag (for example {@code @since}) 114 * to be present on method javadocs also in addition to default tokens. 115 * </p> 116 * <pre> 117 * <module name="WriteTag"> 118 * <property name="tag" value="@since"/> 119 * <property name="tokens" 120 * value="INTERFACE_DEF, CLASS_DEF, ENUM_DEF, ANNOTATION_DEF, RECORD_DEF, METHOD_DEF" /> 121 * </module> 122 * </pre> 123 * <p> 124 * Example: 125 * </p> 126 * <pre> 127 * /** 128 * * Some class 129 * */ 130 * public class Test { // violation as required tag is missed 131 * /** some doc */ 132 * void foo() {} // violation as required tag is missed 133 * } 134 * </pre> 135 * <p> 136 * To configure Check to demand {@code @since} tag 137 * to be present with digital value on method javadocs also in addition to default tokens. 138 * Attention: usage of non "ignore" in tagSeverity will print violation with such severity 139 * on each presence of such tag. 140 * </p> 141 * <pre> 142 * <module name="WriteTag"> 143 * <property name="tag" value="@since"/> 144 * <property name="tokens" 145 * value="INTERFACE_DEF, CLASS_DEF, ENUM_DEF, ANNOTATION_DEF, RECORD_DEF, METHOD_DEF" /> 146 * <property name="tagFormat" value="[1-9\.]"/> 147 * <property name="tagSeverity" value="ignore"/> 148 * </module> 149 * </pre> 150 * <p> 151 * Example: 152 * </p> 153 * <pre> 154 * /** 155 * * Some class 156 * * @since 1.2 157 * */ 158 * public class Test { 159 * /** some doc 160 * * @since violation 161 * */ 162 * void foo() {} 163 * } 164 * </pre> 165 * <p> 166 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 167 * </p> 168 * <p> 169 * Violation Message Keys: 170 * </p> 171 * <ul> 172 * <li> 173 * {@code javadoc.writeTag} 174 * </li> 175 * <li> 176 * {@code type.missingTag} 177 * </li> 178 * <li> 179 * {@code type.tagFormat} 180 * </li> 181 * </ul> 182 * 183 * @since 4.2 184 */ 185@StatelessCheck 186public class WriteTagCheck 187 extends AbstractCheck { 188 189 /** 190 * A key is pointing to the warning message text in "messages.properties" 191 * file. 192 */ 193 public static final String MSG_MISSING_TAG = "type.missingTag"; 194 195 /** 196 * A key is pointing to the warning message text in "messages.properties" 197 * file. 198 */ 199 public static final String MSG_WRITE_TAG = "javadoc.writeTag"; 200 201 /** 202 * A key is pointing to the warning message text in "messages.properties" 203 * file. 204 */ 205 public static final String MSG_TAG_FORMAT = "type.tagFormat"; 206 207 /** Compiled regexp to match tag. */ 208 private Pattern tagRegExp; 209 /** Specify the regexp to match tag content. */ 210 private Pattern tagFormat; 211 212 /** Specify the name of tag. */ 213 private String tag; 214 /** Specify the severity level when tag is found and printed. */ 215 private SeverityLevel tagSeverity = SeverityLevel.INFO; 216 217 /** 218 * Setter to specify the name of tag. 219 * 220 * @param tag tag to check 221 */ 222 public void setTag(String tag) { 223 this.tag = tag; 224 tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)"); 225 } 226 227 /** 228 * Setter to specify the regexp to match tag content. 229 * 230 * @param pattern a {@code String} value 231 */ 232 public void setTagFormat(Pattern pattern) { 233 tagFormat = pattern; 234 } 235 236 /** 237 * Setter to specify the severity level when tag is found and printed. 238 * 239 * @param severity The new severity level 240 * @see SeverityLevel 241 */ 242 public final void setTagSeverity(SeverityLevel severity) { 243 tagSeverity = severity; 244 } 245 246 @Override 247 public int[] getDefaultTokens() { 248 return new int[] { 249 TokenTypes.INTERFACE_DEF, 250 TokenTypes.CLASS_DEF, 251 TokenTypes.ENUM_DEF, 252 TokenTypes.ANNOTATION_DEF, 253 TokenTypes.RECORD_DEF, 254 }; 255 } 256 257 @Override 258 public int[] getAcceptableTokens() { 259 return new int[] { 260 TokenTypes.INTERFACE_DEF, 261 TokenTypes.CLASS_DEF, 262 TokenTypes.ENUM_DEF, 263 TokenTypes.ANNOTATION_DEF, 264 TokenTypes.METHOD_DEF, 265 TokenTypes.CTOR_DEF, 266 TokenTypes.ENUM_CONSTANT_DEF, 267 TokenTypes.ANNOTATION_FIELD_DEF, 268 TokenTypes.RECORD_DEF, 269 TokenTypes.COMPACT_CTOR_DEF, 270 }; 271 } 272 273 @Override 274 public int[] getRequiredTokens() { 275 return CommonUtil.EMPTY_INT_ARRAY; 276 } 277 278 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 279 @SuppressWarnings("deprecation") 280 @Override 281 public void visitToken(DetailAST ast) { 282 final FileContents contents = getFileContents(); 283 final int lineNo = ast.getLineNo(); 284 final TextBlock cmt = 285 contents.getJavadocBefore(lineNo); 286 if (cmt == null) { 287 log(lineNo, MSG_MISSING_TAG, tag); 288 } 289 else { 290 checkTag(lineNo, cmt.getText()); 291 } 292 } 293 294 /** 295 * Verifies that a type definition has a required tag. 296 * 297 * @param lineNo the line number for the type definition. 298 * @param comment the Javadoc comment for the type definition. 299 */ 300 private void checkTag(int lineNo, String... comment) { 301 if (tagRegExp != null) { 302 boolean hasTag = false; 303 for (int i = 0; i < comment.length; i++) { 304 final String commentValue = comment[i]; 305 final Matcher matcher = tagRegExp.matcher(commentValue); 306 if (matcher.find()) { 307 hasTag = true; 308 final int contentStart = matcher.start(1); 309 final String content = commentValue.substring(contentStart); 310 if (tagFormat == null || tagFormat.matcher(content).find()) { 311 logTag(lineNo + i - comment.length, tag, content); 312 } 313 else { 314 log(lineNo + i - comment.length, MSG_TAG_FORMAT, tag, tagFormat.pattern()); 315 } 316 } 317 } 318 if (!hasTag) { 319 log(lineNo, MSG_MISSING_TAG, tag); 320 } 321 } 322 } 323 324 /** 325 * Log a message. 326 * 327 * @param line the line number where the violation was found 328 * @param tagName the javadoc tag to be logged 329 * @param tagValue the contents of the tag 330 * 331 * @see java.text.MessageFormat 332 */ 333 private void logTag(int line, String tagName, String tagValue) { 334 final String originalSeverity = getSeverity(); 335 setSeverity(tagSeverity.getName()); 336 337 log(line, MSG_WRITE_TAG, tagName, tagValue); 338 339 setSeverity(originalSeverity); 340 } 341 342}