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