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