001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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 * regular expression</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 @Override 363 public void visitToken(DetailAST ast) { 364 if (shouldCheck(ast)) { 365 final FileContents contents = getFileContents(); 366 final int lineNo = ast.getLineNo(); 367 final TextBlock textBlock = contents.getJavadocBefore(lineNo); 368 if (textBlock != null) { 369 final List<JavadocTag> tags = getJavadocTags(textBlock); 370 if (ScopeUtil.isOuterMostType(ast)) { 371 // don't check author/version for inner classes 372 checkTag(ast, tags, JavadocTagInfo.AUTHOR.getName(), 373 authorFormat); 374 checkTag(ast, tags, JavadocTagInfo.VERSION.getName(), 375 versionFormat); 376 } 377 378 final List<String> typeParamNames = 379 CheckUtil.getTypeParameterNames(ast); 380 final List<String> recordComponentNames = 381 getRecordComponentNames(ast); 382 383 if (!allowMissingParamTags) { 384 385 typeParamNames.forEach(typeParamName -> { 386 checkTypeParamTag(ast, tags, typeParamName); 387 }); 388 389 recordComponentNames.forEach(componentName -> { 390 checkComponentParamTag(ast, tags, componentName); 391 }); 392 } 393 394 checkUnusedParamTags(tags, typeParamNames, recordComponentNames); 395 } 396 } 397 } 398 399 /** 400 * Whether we should check this node. 401 * 402 * @param ast a given node. 403 * @return whether we should check a given node. 404 */ 405 private boolean shouldCheck(DetailAST ast) { 406 final Scope customScope = ScopeUtil.getScope(ast); 407 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 408 409 return customScope.isIn(scope) 410 && (surroundingScope == null || surroundingScope.isIn(scope)) 411 && (excludeScope == null 412 || !customScope.isIn(excludeScope) 413 || surroundingScope != null 414 && !surroundingScope.isIn(excludeScope)) 415 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); 416 } 417 418 /** 419 * Gets all standalone tags from a given javadoc. 420 * 421 * @param textBlock the Javadoc comment to process. 422 * @return all standalone tags from the given javadoc. 423 */ 424 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 425 final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock, 426 JavadocUtil.JavadocTagType.BLOCK); 427 if (!allowUnknownTags) { 428 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 429 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 430 tag.getName()); 431 } 432 } 433 return tags.getValidTags(); 434 } 435 436 /** 437 * Verifies that a type definition has a required tag. 438 * 439 * @param ast the AST node for the type definition. 440 * @param tags tags from the Javadoc comment for the type definition. 441 * @param tagName the required tag name. 442 * @param formatPattern regexp for the tag value. 443 */ 444 private void checkTag(DetailAST ast, List<JavadocTag> tags, String tagName, 445 Pattern formatPattern) { 446 if (formatPattern != null) { 447 boolean hasTag = false; 448 final String tagPrefix = "@"; 449 450 for (final JavadocTag tag :tags) { 451 if (tag.getTagName().equals(tagName)) { 452 hasTag = true; 453 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 454 log(ast, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 455 } 456 } 457 } 458 if (!hasTag) { 459 log(ast, MSG_MISSING_TAG, tagPrefix + tagName); 460 } 461 } 462 } 463 464 /** 465 * Verifies that a record definition has the specified param tag for 466 * the specified record component name. 467 * 468 * @param ast the AST node for the record definition. 469 * @param tags tags from the Javadoc comment for the record definition. 470 * @param recordComponentName the name of the type parameter 471 */ 472 private void checkComponentParamTag(DetailAST ast, 473 List<JavadocTag> tags, 474 String recordComponentName) { 475 476 final boolean found = tags 477 .stream() 478 .filter(JavadocTag::isParamTag) 479 .anyMatch(tag -> tag.getFirstArg().indexOf(recordComponentName) == 0); 480 481 if (!found) { 482 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 483 + SPACE + recordComponentName); 484 } 485 } 486 487 /** 488 * Verifies that a type definition has the specified param tag for 489 * the specified type parameter name. 490 * 491 * @param ast the AST node for the type definition. 492 * @param tags tags from the Javadoc comment for the type definition. 493 * @param typeParamName the name of the type parameter 494 */ 495 private void checkTypeParamTag(DetailAST ast, 496 List<JavadocTag> tags, String typeParamName) { 497 final String typeParamNameWithBrackets = 498 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET; 499 500 final boolean found = tags 501 .stream() 502 .filter(JavadocTag::isParamTag) 503 .anyMatch(tag -> tag.getFirstArg().indexOf(typeParamNameWithBrackets) == 0); 504 505 if (!found) { 506 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 507 + SPACE + typeParamNameWithBrackets); 508 } 509 } 510 511 /** 512 * Checks for unused param tags for type parameters and record components. 513 * 514 * @param tags tags from the Javadoc comment for the type definition. 515 * @param typeParamNames names of type parameters 516 * @param recordComponentNames list of record component names in this definition 517 */ 518 private void checkUnusedParamTags( 519 List<JavadocTag> tags, 520 List<String> typeParamNames, 521 List<String> recordComponentNames) { 522 523 for (final JavadocTag tag: tags) { 524 if (tag.isParamTag()) { 525 final String paramName = extractParamNameFromTag(tag); 526 final boolean found = typeParamNames.contains(paramName) 527 || recordComponentNames.contains(paramName); 528 529 if (!found) { 530 final String actualParamName = 531 TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 532 log(tag.getLineNo(), tag.getColumnNo(), 533 MSG_UNUSED_TAG, 534 JavadocTagInfo.PARAM.getText(), actualParamName); 535 } 536 } 537 } 538 539 } 540 541 /** 542 * Extracts parameter name from tag. 543 * 544 * @param tag javadoc tag to extract parameter name 545 * @return extracts type parameter name from tag 546 */ 547 private static String extractParamNameFromTag(JavadocTag tag) { 548 final String typeParamName; 549 final Matcher matchInAngleBrackets = 550 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 551 if (matchInAngleBrackets.find()) { 552 typeParamName = matchInAngleBrackets.group(1).trim(); 553 } 554 else { 555 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 556 } 557 return typeParamName; 558 } 559 560 /** 561 * Collects the record components in a record definition. 562 * 563 * @param node the possible record definition ast. 564 * @return the list of record components in this record definition. 565 */ 566 private static List<String> getRecordComponentNames(DetailAST node) { 567 final DetailAST components = node.findFirstToken(TokenTypes.RECORD_COMPONENTS); 568 final List<String> componentList = new ArrayList<>(); 569 570 if (components != null) { 571 TokenUtil.forEachChild(components, 572 TokenTypes.RECORD_COMPONENT_DEF, component -> { 573 final DetailAST ident = component.findFirstToken(TokenTypes.IDENT); 574 componentList.add(ident.getText()); 575 }); 576 } 577 578 return componentList; 579 } 580}