001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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("\\s*<([^>]+)>.*"); 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 customScope = ScopeUtil.getScope(ast); 361 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 362 363 return customScope.isIn(scope) 364 && (surroundingScope == null || surroundingScope.isIn(scope)) 365 && (excludeScope == null 366 || !customScope.isIn(excludeScope) 367 || surroundingScope != null 368 && !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 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 383 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 384 tag.getName()); 385 } 386 } 387 return tags.getValidTags(); 388 } 389 390 /** 391 * Verifies that a type definition has a required tag. 392 * 393 * @param ast the AST node for the type definition. 394 * @param tags tags from the Javadoc comment for the type definition. 395 * @param tagName the required tag name. 396 * @param formatPattern regexp for the tag value. 397 */ 398 private void checkTag(DetailAST ast, Iterable<JavadocTag> tags, String tagName, 399 Pattern formatPattern) { 400 if (formatPattern != null) { 401 boolean hasTag = false; 402 final String tagPrefix = "@"; 403 404 for (final JavadocTag tag :tags) { 405 if (tag.getTagName().equals(tagName)) { 406 hasTag = true; 407 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 408 log(ast, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 409 } 410 } 411 } 412 if (!hasTag) { 413 log(ast, MSG_MISSING_TAG, tagPrefix + tagName); 414 } 415 } 416 } 417 418 /** 419 * Verifies that a record definition has the specified param tag for 420 * the specified record component name. 421 * 422 * @param ast the AST node for the record definition. 423 * @param tags tags from the Javadoc comment for the record definition. 424 * @param recordComponentName the name of the type parameter 425 */ 426 private void checkComponentParamTag(DetailAST ast, 427 Collection<JavadocTag> tags, 428 String recordComponentName) { 429 430 final boolean found = tags 431 .stream() 432 .filter(JavadocTag::isParamTag) 433 .anyMatch(tag -> tag.getFirstArg().indexOf(recordComponentName) == 0); 434 435 if (!found) { 436 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 437 + SPACE + recordComponentName); 438 } 439 } 440 441 /** 442 * Verifies that a type definition has the specified param tag for 443 * the specified type parameter name. 444 * 445 * @param ast the AST node for the type definition. 446 * @param tags tags from the Javadoc comment for the type definition. 447 * @param typeParamName the name of the type parameter 448 */ 449 private void checkTypeParamTag(DetailAST ast, 450 Collection<JavadocTag> tags, String typeParamName) { 451 final String typeParamNameWithBrackets = 452 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET; 453 454 final boolean found = tags 455 .stream() 456 .filter(JavadocTag::isParamTag) 457 .anyMatch(tag -> tag.getFirstArg().indexOf(typeParamNameWithBrackets) == 0); 458 459 if (!found) { 460 log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 461 + SPACE + typeParamNameWithBrackets); 462 } 463 } 464 465 /** 466 * Checks for unused param tags for type parameters and record components. 467 * 468 * @param tags tags from the Javadoc comment for the type definition 469 * @param typeParamNames names of type parameters 470 * @param recordComponentNames record component names in this definition 471 */ 472 private void checkUnusedParamTags( 473 List<JavadocTag> tags, 474 List<String> typeParamNames, 475 List<String> recordComponentNames) { 476 477 for (final JavadocTag tag: tags) { 478 if (tag.isParamTag()) { 479 final String paramName = extractParamNameFromTag(tag); 480 final boolean found = typeParamNames.contains(paramName) 481 || recordComponentNames.contains(paramName); 482 483 if (!found) { 484 final String actualParamName = 485 TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 486 log(tag.getLineNo(), tag.getColumnNo(), 487 MSG_UNUSED_TAG, 488 JavadocTagInfo.PARAM.getText(), actualParamName); 489 } 490 } 491 } 492 493 } 494 495 /** 496 * Extracts parameter name from tag. 497 * 498 * @param tag javadoc tag to extract parameter name 499 * @return extracts type parameter name from tag 500 */ 501 private static String extractParamNameFromTag(JavadocTag tag) { 502 final String typeParamName; 503 final Matcher matchInAngleBrackets = 504 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 505 if (matchInAngleBrackets.find()) { 506 typeParamName = matchInAngleBrackets.group(1).trim(); 507 } 508 else { 509 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 510 } 511 return typeParamName; 512 } 513 514 /** 515 * Collects the record components in a record definition. 516 * 517 * @param node the possible record definition ast. 518 * @return the record components in this record definition. 519 */ 520 private static List<String> getRecordComponentNames(DetailAST node) { 521 final DetailAST components = node.findFirstToken(TokenTypes.RECORD_COMPONENTS); 522 final List<String> componentList = new ArrayList<>(); 523 524 if (components != null) { 525 TokenUtil.forEachChild(components, 526 TokenTypes.RECORD_COMPONENT_DEF, component -> { 527 final DetailAST ident = component.findFirstToken(TokenTypes.IDENT); 528 componentList.add(ident.getText()); 529 }); 530 } 531 532 return componentList; 533 } 534}