001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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; 407 408 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 409 customScope = Scope.PUBLIC; 410 } 411 else { 412 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 413 customScope = ScopeUtil.getScopeFromMods(mods); 414 } 415 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 416 417 return customScope.isIn(scope) 418 && (surroundingScope == null || surroundingScope.isIn(scope)) 419 && (excludeScope == null 420 || !customScope.isIn(excludeScope) 421 || surroundingScope != null 422 && !surroundingScope.isIn(excludeScope)) 423 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); 424 } 425 426 /** 427 * Gets all standalone tags from a given javadoc. 428 * 429 * @param textBlock the Javadoc comment to process. 430 * @return all standalone tags from the given javadoc. 431 */ 432 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 433 final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock, 434 JavadocUtil.JavadocTagType.BLOCK); 435 if (!allowUnknownTags) { 436 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 437 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 438 tag.getName()); 439 } 440 } 441 return tags.getValidTags(); 442 } 443 444 /** 445 * Verifies that a type definition has a required tag. 446 * 447 * @param ast the AST node for the type definition. 448 * @param tags tags from the Javadoc comment for the type definition. 449 * @param tagName the required tag name. 450 * @param formatPattern regexp for the tag value. 451 */ 452 private void checkTag(DetailAST ast, List<JavadocTag> tags, String tagName, 453 Pattern formatPattern) { 454 if (formatPattern != null) { 455 boolean hasTag = false; 456 final String tagPrefix = "@"; 457 458 for (final JavadocTag tag :tags) { 459 if (tag.getTagName().equals(tagName)) { 460 hasTag = true; 461 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 462 log(ast, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 463 } 464 } 465 } 466 if (!hasTag) { 467 log(ast, MSG_MISSING_TAG, tagPrefix + tagName); 468 } 469 } 470 } 471 472 /** 473 * Verifies that a record definition has the specified param tag for 474 * the specified record component name. 475 * 476 * @param ast the AST node for the record definition. 477 * @param tags tags from the Javadoc comment for the record definition. 478 * @param recordComponentName the name of the type parameter 479 */ 480 private void checkComponentParamTag(DetailAST ast, 481 List<JavadocTag> tags, 482 String recordComponentName) { 483 484 final boolean found = tags 485 .stream() 486 .filter(JavadocTag::isParamTag) 487 .anyMatch(tag -> tag.getFirstArg().indexOf(recordComponentName) == 0); 488 489 if (!found) { 490 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 491 + SPACE + recordComponentName); 492 } 493 } 494 495 /** 496 * Verifies that a type definition has the specified param tag for 497 * the specified type parameter name. 498 * 499 * @param ast the AST node for the type definition. 500 * @param tags tags from the Javadoc comment for the type definition. 501 * @param typeParamName the name of the type parameter 502 */ 503 private void checkTypeParamTag(DetailAST ast, 504 List<JavadocTag> tags, String typeParamName) { 505 final String typeParamNameWithBrackets = 506 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET; 507 508 final boolean found = tags 509 .stream() 510 .filter(JavadocTag::isParamTag) 511 .anyMatch(tag -> tag.getFirstArg().indexOf(typeParamNameWithBrackets) == 0); 512 513 if (!found) { 514 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 515 + SPACE + typeParamNameWithBrackets); 516 } 517 } 518 519 /** 520 * Checks for unused param tags for type parameters and record components. 521 * 522 * @param tags tags from the Javadoc comment for the type definition. 523 * @param typeParamNames names of type parameters 524 * @param recordComponentNames list of record component names in this definition 525 */ 526 private void checkUnusedParamTags( 527 List<JavadocTag> tags, 528 List<String> typeParamNames, 529 List<String> recordComponentNames) { 530 531 for (final JavadocTag tag: tags) { 532 if (tag.isParamTag()) { 533 final String paramName = extractParamNameFromTag(tag); 534 final boolean found = typeParamNames.contains(paramName) 535 || recordComponentNames.contains(paramName); 536 537 if (!found) { 538 final String actualParamName = 539 TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 540 log(tag.getLineNo(), tag.getColumnNo(), 541 MSG_UNUSED_TAG, 542 JavadocTagInfo.PARAM.getText(), actualParamName); 543 } 544 } 545 } 546 547 } 548 549 /** 550 * Extracts parameter name from tag. 551 * 552 * @param tag javadoc tag to extract parameter name 553 * @return extracts type parameter name from tag 554 */ 555 private static String extractParamNameFromTag(JavadocTag tag) { 556 final String typeParamName; 557 final Matcher matchInAngleBrackets = 558 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 559 if (matchInAngleBrackets.find()) { 560 typeParamName = matchInAngleBrackets.group(1).trim(); 561 } 562 else { 563 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 564 } 565 return typeParamName; 566 } 567 568 /** 569 * Collects the record components in a record definition. 570 * 571 * @param node the possible record definition ast. 572 * @return the list of record components in this record definition. 573 */ 574 private static List<String> getRecordComponentNames(DetailAST node) { 575 final DetailAST components = node.findFirstToken(TokenTypes.RECORD_COMPONENTS); 576 final List<String> componentList = new ArrayList<>(); 577 578 if (components != null) { 579 TokenUtil.forEachChild(components, 580 TokenTypes.RECORD_COMPONENT_DEF, component -> { 581 final DetailAST ident = component.findFirstToken(TokenTypes.IDENT); 582 componentList.add(ident.getText()); 583 }); 584 } 585 586 return componentList; 587 } 588}