001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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.ArrayList; 023import java.util.Collection; 024import java.util.List; 025import java.util.Set; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import com.puppycrawl.tools.checkstyle.StatelessCheck; 030import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 031import com.puppycrawl.tools.checkstyle.api.DetailAST; 032import com.puppycrawl.tools.checkstyle.api.FileContents; 033import com.puppycrawl.tools.checkstyle.api.Scope; 034import com.puppycrawl.tools.checkstyle.api.TextBlock; 035import com.puppycrawl.tools.checkstyle.api.TokenTypes; 036import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 037import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 038import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 039import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 040import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 041import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 042 043/** 044 * <div> 045 * Checks the Javadoc comments for type definitions. By default, does 046 * not check for author or version tags. The scope to verify is specified using the {@code Scope} 047 * class and defaults to {@code Scope.PRIVATE}. To verify another scope, set property 048 * scope to one of the {@code Scope} constants. To define the format for an author 049 * tag or a version tag, set property authorFormat or versionFormat respectively to a 050 * <a href="https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html"> 051 * pattern</a>. 052 * </div> 053 * 054 * <p> 055 * Does not perform checks for author and version tags for inner classes, 056 * as they should be redundant because of outer class. 057 * </p> 058 * 059 * <p> 060 * Does not perform checks for type definitions that do not have any Javadoc comments. 061 * </p> 062 * 063 * <p> 064 * Error messages about type parameters and record components for which no param tags are present 065 * can be suppressed by defining property {@code allowMissingParamTags}. 066 * </p> 067 * <ul> 068 * <li> 069 * Property {@code allowMissingParamTags} - Control whether to ignore violations 070 * when a class has type parameters but does not have matching param tags in the Javadoc. 071 * Type is {@code boolean}. 072 * Default value is {@code false}. 073 * </li> 074 * <li> 075 * Property {@code allowUnknownTags} - Control whether to ignore violations when 076 * a Javadoc tag is not recognised. 077 * Type is {@code boolean}. 078 * Default value is {@code false}. 079 * </li> 080 * <li> 081 * Property {@code allowedAnnotations} - Specify annotations that allow 082 * skipping validation at all. Only short names are allowed, e.g. {@code Generated}. 083 * Type is {@code java.lang.String[]}. 084 * Default value is {@code Generated}. 085 * </li> 086 * <li> 087 * Property {@code authorFormat} - Specify the pattern for {@code @author} tag. 088 * Type is {@code java.util.regex.Pattern}. 089 * Default value is {@code null}. 090 * </li> 091 * <li> 092 * Property {@code excludeScope} - Specify the visibility scope where Javadoc 093 * comments are not checked. 094 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 095 * Default value is {@code null}. 096 * </li> 097 * <li> 098 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked. 099 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 100 * Default value is {@code private}. 101 * </li> 102 * <li> 103 * Property {@code versionFormat} - Specify the pattern for {@code @version} tag. 104 * Type is {@code java.util.regex.Pattern}. 105 * Default value is {@code null}. 106 * </li> 107 * <li> 108 * Property {@code tokens} - tokens to check 109 * Type is {@code java.lang.String[]}. 110 * Validation type is {@code tokenSet}. 111 * Default value is: 112 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 113 * INTERFACE_DEF</a>, 114 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 115 * CLASS_DEF</a>, 116 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 117 * ENUM_DEF</a>, 118 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 119 * ANNOTATION_DEF</a>, 120 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 121 * RECORD_DEF</a>. 122 * </li> 123 * </ul> 124 * 125 * <p> 126 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 127 * </p> 128 * 129 * <p> 130 * Violation Message Keys: 131 * </p> 132 * <ul> 133 * <li> 134 * {@code javadoc.unknownTag} 135 * </li> 136 * <li> 137 * {@code javadoc.unusedTag} 138 * </li> 139 * <li> 140 * {@code javadoc.unusedTagGeneral} 141 * </li> 142 * <li> 143 * {@code type.missingTag} 144 * </li> 145 * <li> 146 * {@code type.tagFormat} 147 * </li> 148 * </ul> 149 * 150 * @since 3.0 151 * 152 */ 153@StatelessCheck 154public class JavadocTypeCheck 155 extends AbstractCheck { 156 157 /** 158 * A key is pointing to the warning message text in "messages.properties" 159 * file. 160 */ 161 public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag"; 162 163 /** 164 * A key is pointing to the warning message text in "messages.properties" 165 * file. 166 */ 167 public static final String MSG_TAG_FORMAT = "type.tagFormat"; 168 169 /** 170 * A key is pointing to the warning message text in "messages.properties" 171 * file. 172 */ 173 public static final String MSG_MISSING_TAG = "type.missingTag"; 174 175 /** 176 * A key is pointing to the warning message text in "messages.properties" 177 * file. 178 */ 179 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag"; 180 181 /** 182 * A key is pointing to the warning message text in "messages.properties" 183 * file. 184 */ 185 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral"; 186 187 /** Open angle bracket literal. */ 188 private static final String OPEN_ANGLE_BRACKET = "<"; 189 190 /** Close angle bracket literal. */ 191 private static final String CLOSE_ANGLE_BRACKET = ">"; 192 193 /** Space literal. */ 194 private static final String SPACE = " "; 195 196 /** Pattern to match type name within angle brackets in javadoc param tag. */ 197 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG = 198 Pattern.compile("^<([^>]+)"); 199 200 /** Pattern to split type name field in javadoc param tag. */ 201 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER = 202 Pattern.compile("\\s+"); 203 204 /** Specify the visibility scope where Javadoc comments are checked. */ 205 private Scope scope = Scope.PRIVATE; 206 /** Specify the visibility scope where Javadoc comments are not checked. */ 207 private Scope excludeScope; 208 /** Specify the pattern for {@code @author} tag. */ 209 private Pattern authorFormat; 210 /** Specify the pattern for {@code @version} tag. */ 211 private Pattern versionFormat; 212 /** 213 * Control whether to ignore violations when a class has type parameters but 214 * does not have matching param tags in the Javadoc. 215 */ 216 private boolean allowMissingParamTags; 217 /** Control whether to ignore violations when a Javadoc tag is not recognised. */ 218 private boolean allowUnknownTags; 219 220 /** 221 * Specify annotations that allow skipping validation at all. 222 * Only short names are allowed, e.g. {@code Generated}. 223 */ 224 private Set<String> allowedAnnotations = Set.of("Generated"); 225 226 /** 227 * Setter to specify the visibility scope where Javadoc comments are checked. 228 * 229 * @param scope a scope. 230 * @since 3.0 231 */ 232 public void setScope(Scope scope) { 233 this.scope = scope; 234 } 235 236 /** 237 * Setter to specify the visibility scope where Javadoc comments are not checked. 238 * 239 * @param excludeScope a scope. 240 * @since 3.4 241 */ 242 public void setExcludeScope(Scope excludeScope) { 243 this.excludeScope = excludeScope; 244 } 245 246 /** 247 * Setter to specify the pattern for {@code @author} tag. 248 * 249 * @param pattern a pattern. 250 * @since 3.0 251 */ 252 public void setAuthorFormat(Pattern pattern) { 253 authorFormat = pattern; 254 } 255 256 /** 257 * Setter to specify the pattern for {@code @version} tag. 258 * 259 * @param pattern a pattern. 260 * @since 3.0 261 */ 262 public void setVersionFormat(Pattern pattern) { 263 versionFormat = pattern; 264 } 265 266 /** 267 * Setter to control whether to ignore violations when a class has type parameters but 268 * does not have matching param tags in the Javadoc. 269 * 270 * @param flag a {@code Boolean} value 271 * @since 4.0 272 */ 273 public void setAllowMissingParamTags(boolean flag) { 274 allowMissingParamTags = flag; 275 } 276 277 /** 278 * Setter to control whether to ignore violations when a Javadoc tag is not recognised. 279 * 280 * @param flag a {@code Boolean} value 281 * @since 5.1 282 */ 283 public void setAllowUnknownTags(boolean flag) { 284 allowUnknownTags = flag; 285 } 286 287 /** 288 * Setter to specify annotations that allow skipping validation at all. 289 * Only short names are allowed, e.g. {@code Generated}. 290 * 291 * @param userAnnotations user's value. 292 * @since 8.15 293 */ 294 public void setAllowedAnnotations(String... userAnnotations) { 295 allowedAnnotations = Set.of(userAnnotations); 296 } 297 298 @Override 299 public int[] getDefaultTokens() { 300 return getAcceptableTokens(); 301 } 302 303 @Override 304 public int[] getAcceptableTokens() { 305 return new int[] { 306 TokenTypes.INTERFACE_DEF, 307 TokenTypes.CLASS_DEF, 308 TokenTypes.ENUM_DEF, 309 TokenTypes.ANNOTATION_DEF, 310 TokenTypes.RECORD_DEF, 311 }; 312 } 313 314 @Override 315 public int[] getRequiredTokens() { 316 return CommonUtil.EMPTY_INT_ARRAY; 317 } 318 319 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 320 @SuppressWarnings("deprecation") 321 @Override 322 public void visitToken(DetailAST ast) { 323 if (shouldCheck(ast)) { 324 final FileContents contents = getFileContents(); 325 final int lineNo = ast.getLineNo(); 326 final TextBlock textBlock = contents.getJavadocBefore(lineNo); 327 if (textBlock != null) { 328 final List<JavadocTag> tags = getJavadocTags(textBlock); 329 if (ScopeUtil.isOuterMostType(ast)) { 330 // don't check author/version for inner classes 331 checkTag(ast, tags, JavadocTagInfo.AUTHOR.getName(), 332 authorFormat); 333 checkTag(ast, tags, JavadocTagInfo.VERSION.getName(), 334 versionFormat); 335 } 336 337 final List<String> typeParamNames = 338 CheckUtil.getTypeParameterNames(ast); 339 final List<String> recordComponentNames = 340 getRecordComponentNames(ast); 341 342 if (!allowMissingParamTags) { 343 344 typeParamNames.forEach(typeParamName -> { 345 checkTypeParamTag(ast, tags, typeParamName); 346 }); 347 348 recordComponentNames.forEach(componentName -> { 349 checkComponentParamTag(ast, tags, componentName); 350 }); 351 } 352 353 checkUnusedParamTags(tags, typeParamNames, recordComponentNames); 354 } 355 } 356 } 357 358 /** 359 * Whether we should check this node. 360 * 361 * @param ast a given node. 362 * @return whether we should check a given node. 363 */ 364 private boolean shouldCheck(DetailAST ast) { 365 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 366 367 return surroundingScope.isIn(scope) 368 && (excludeScope == null || !surroundingScope.isIn(excludeScope)) 369 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); 370 } 371 372 /** 373 * Gets all standalone tags from a given javadoc. 374 * 375 * @param textBlock the Javadoc comment to process. 376 * @return all standalone tags from the given javadoc. 377 */ 378 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 379 final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock, 380 JavadocUtil.JavadocTagType.BLOCK); 381 if (!allowUnknownTags) { 382 tags.getInvalidTags().forEach(tag -> { 383 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, tag.getName()); 384 }); 385 } 386 return tags.getValidTags(); 387 } 388 389 /** 390 * Verifies that a type definition has a required tag. 391 * 392 * @param ast the AST node for the type definition. 393 * @param tags tags from the Javadoc comment for the type definition. 394 * @param tagName the required tag name. 395 * @param formatPattern regexp for the tag value. 396 */ 397 private void checkTag(DetailAST ast, Iterable<JavadocTag> tags, String tagName, 398 Pattern formatPattern) { 399 if (formatPattern != null) { 400 boolean hasTag = false; 401 final String tagPrefix = "@"; 402 403 for (final JavadocTag tag :tags) { 404 if (tag.getTagName().equals(tagName)) { 405 hasTag = true; 406 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 407 log(ast, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 408 } 409 } 410 } 411 if (!hasTag) { 412 log(ast, MSG_MISSING_TAG, tagPrefix + tagName); 413 } 414 } 415 } 416 417 /** 418 * Verifies that a record definition has the specified param tag for 419 * the specified record component name. 420 * 421 * @param ast the AST node for the record definition. 422 * @param tags tags from the Javadoc comment for the record definition. 423 * @param recordComponentName the name of the type parameter 424 */ 425 private void checkComponentParamTag(DetailAST ast, 426 Collection<JavadocTag> tags, 427 String recordComponentName) { 428 429 final boolean found = tags 430 .stream() 431 .filter(JavadocTag::isParamTag) 432 .anyMatch(tag -> tag.getFirstArg().indexOf(recordComponentName) == 0); 433 434 if (!found) { 435 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 436 + SPACE + recordComponentName); 437 } 438 } 439 440 /** 441 * Verifies that a type definition has the specified param tag for 442 * the specified type parameter name. 443 * 444 * @param ast the AST node for the type definition. 445 * @param tags tags from the Javadoc comment for the type definition. 446 * @param typeParamName the name of the type parameter 447 */ 448 private void checkTypeParamTag(DetailAST ast, 449 Collection<JavadocTag> tags, String typeParamName) { 450 final String typeParamNameWithBrackets = 451 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET; 452 453 final boolean found = tags 454 .stream() 455 .filter(JavadocTag::isParamTag) 456 .anyMatch(tag -> tag.getFirstArg().indexOf(typeParamNameWithBrackets) == 0); 457 458 if (!found) { 459 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 460 + SPACE + typeParamNameWithBrackets); 461 } 462 } 463 464 /** 465 * Checks for unused param tags for type parameters and record components. 466 * 467 * @param tags tags from the Javadoc comment for the type definition 468 * @param typeParamNames names of type parameters 469 * @param recordComponentNames record component names in this definition 470 */ 471 private void checkUnusedParamTags( 472 List<JavadocTag> tags, 473 List<String> typeParamNames, 474 List<String> recordComponentNames) { 475 476 for (final JavadocTag tag: tags) { 477 if (tag.isParamTag()) { 478 final String paramName = extractParamNameFromTag(tag); 479 final boolean found = typeParamNames.contains(paramName) 480 || recordComponentNames.contains(paramName); 481 482 if (!found) { 483 if (paramName.isEmpty()) { 484 log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG_GENERAL); 485 } 486 else { 487 final String actualParamName = 488 TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 489 log(tag.getLineNo(), tag.getColumnNo(), 490 MSG_UNUSED_TAG, 491 JavadocTagInfo.PARAM.getText(), actualParamName); 492 } 493 } 494 } 495 } 496 497 } 498 499 /** 500 * Extracts parameter name from tag. 501 * 502 * @param tag javadoc tag to extract parameter name 503 * @return extracts type parameter name from tag 504 */ 505 private static String extractParamNameFromTag(JavadocTag tag) { 506 final String typeParamName; 507 final Matcher matchInAngleBrackets = 508 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 509 if (matchInAngleBrackets.find()) { 510 typeParamName = matchInAngleBrackets.group(1).trim(); 511 } 512 else { 513 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 514 } 515 return typeParamName; 516 } 517 518 /** 519 * Collects the record components in a record definition. 520 * 521 * @param node the possible record definition ast. 522 * @return the record components in this record definition. 523 */ 524 private static List<String> getRecordComponentNames(DetailAST node) { 525 final DetailAST components = node.findFirstToken(TokenTypes.RECORD_COMPONENTS); 526 final List<String> componentList = new ArrayList<>(); 527 528 if (components != null) { 529 TokenUtil.forEachChild(components, 530 TokenTypes.RECORD_COMPONENT_DEF, component -> { 531 final DetailAST ident = component.findFirstToken(TokenTypes.IDENT); 532 componentList.add(ident.getText()); 533 }); 534 } 535 536 return componentList; 537 } 538}