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.Arrays; 023import java.util.Collections; 024import java.util.List; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import com.puppycrawl.tools.checkstyle.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.FileContents; 032import com.puppycrawl.tools.checkstyle.api.Scope; 033import com.puppycrawl.tools.checkstyle.api.TextBlock; 034import com.puppycrawl.tools.checkstyle.api.TokenTypes; 035import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 036import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 037import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 038import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 039import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 040 041/** 042 * <p> 043 * Checks the Javadoc comments for annotation/enum/class/interface definitions. By default, does 044 * not check for author or version tags. The scope to verify is specified using the {@code Scope} 045 * class and defaults to {@code Scope.PRIVATE}. To verify another scope, set property 046 * scope to one of the {@code Scope} constants. To define the format for an author 047 * tag or a version tag, set property authorFormat or versionFormat respectively to a 048 * <a href="https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html"> 049 * regular expression</a>. 050 * </p> 051 * <p> 052 * Does not perform checks for author and version tags for inner classes, 053 * as they should be redundant because of outer class. 054 * </p> 055 * <p> 056 * Error messages about type parameters for which no param tags are present 057 * can be suppressed by defining property {@code allowMissingParamTags}. 058 * </p> 059 * <ul> 060 * <li> 061 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked. 062 * Default value is {@code private}. 063 * </li> 064 * <li> 065 * Property {@code excludeScope} - Specify the visibility scope where Javadoc 066 * comments are not checked. 067 * Default value is {@code null}. 068 * </li> 069 * <li> 070 * Property {@code authorFormat} - Specify the pattern for {@code @author} tag. 071 * Default value is {@code null}. 072 * </li> 073 * <li> 074 * Property {@code versionFormat} - Specify the pattern for {@code @version} tag. 075 * Default value is {@code null}. 076 * </li> 077 * <li> 078 * Property {@code allowMissingParamTags} - Control whether to ignore violations 079 * when a class has type parameters but does not have matching param tags in the Javadoc. 080 * Default value is {@code false}. 081 * </li> 082 * <li> 083 * Property {@code allowUnknownTags} - Control whether to ignore violations when 084 * a Javadoc tag is not recognised. 085 * Default value is {@code false}. 086 * </li> 087 * <li> 088 * Property {@code allowedAnnotations} - Specify the list of annotations that allow 089 * missed documentation. Only short names are allowed, e.g. {@code Generated}. 090 * Default value is {@code Generated}. 091 * </li> 092 * <li> 093 * Property {@code tokens} - tokens to check 094 * Default value is: 095 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 096 * INTERFACE_DEF</a>, 097 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 098 * CLASS_DEF</a>, 099 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 100 * ENUM_DEF</a>, 101 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 102 * ANNOTATION_DEF</a>. 103 * </li> 104 * </ul> 105 * <p> 106 * To configure the default check: 107 * </p> 108 * <pre> 109 * <module name="JavadocType"/> 110 * </pre> 111 * <p> 112 * To configure the check for {@code public} scope: 113 * </p> 114 * <pre> 115 * <module name="JavadocType"> 116 * <property name="scope" value="public"/> 117 * </module> 118 * </pre> 119 * <p> 120 * To configure the check for an {@code @author} tag: 121 * </p> 122 * <pre> 123 * <module name="JavadocType"> 124 * <property name="authorFormat" value="\S"/> 125 * </module> 126 * </pre> 127 * <p> 128 * To configure the check for a CVS revision version tag: 129 * </p> 130 * <pre> 131 * <module name="JavadocType"> 132 * <property name="versionFormat" value="\$Revision.*\$"/> 133 * </module> 134 * </pre> 135 * <p> 136 * To configure the check for {@code private} classes only: 137 * </p> 138 * <pre> 139 * <module name="JavadocType"> 140 * <property name="scope" value="private"/> 141 * <property name="excludeScope" value="package"/> 142 * </module> 143 * </pre> 144 * <p> 145 * Example that allows missing comments for classes annotated with 146 * {@code @SpringBootApplication} and {@code @Configuration}: 147 * </p> 148 * <pre> 149 * @SpringBootApplication // no violations about missing comment on class 150 * public class Application {} 151 * 152 * @Configuration // no violations about missing comment on class 153 * class DatabaseConfiguration {} 154 * </pre> 155 * <p> 156 * Use following configuration: 157 * </p> 158 * <pre> 159 * <module name="JavadocType"> 160 * <property name="allowedAnnotations" value="SpringBootApplication,Configuration"/> 161 * </module> 162 * </pre> 163 * 164 * @since 3.0 165 * 166 */ 167@StatelessCheck 168public class JavadocTypeCheck 169 extends AbstractCheck { 170 171 /** 172 * A key is pointing to the warning message text in "messages.properties" 173 * file. 174 */ 175 public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag"; 176 177 /** 178 * A key is pointing to the warning message text in "messages.properties" 179 * file. 180 */ 181 public static final String MSG_TAG_FORMAT = "type.tagFormat"; 182 183 /** 184 * A key is pointing to the warning message text in "messages.properties" 185 * file. 186 */ 187 public static final String MSG_MISSING_TAG = "type.missingTag"; 188 189 /** 190 * A key is pointing to the warning message text in "messages.properties" 191 * file. 192 */ 193 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag"; 194 195 /** 196 * A key is pointing to the warning message text in "messages.properties" 197 * file. 198 */ 199 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral"; 200 201 /** Open angle bracket literal. */ 202 private static final String OPEN_ANGLE_BRACKET = "<"; 203 204 /** Close angle bracket literal. */ 205 private static final String CLOSE_ANGLE_BRACKET = ">"; 206 207 /** Pattern to match type name within angle brackets in javadoc param tag. */ 208 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG = 209 Pattern.compile("\\s*<([^>]+)>.*"); 210 211 /** Pattern to split type name field in javadoc param tag. */ 212 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER = 213 Pattern.compile("\\s+"); 214 215 /** Specify the visibility scope where Javadoc comments are checked. */ 216 private Scope scope = Scope.PRIVATE; 217 /** Specify the visibility scope where Javadoc comments are not checked. */ 218 private Scope excludeScope; 219 /** Specify the pattern for {@code @author} tag. */ 220 private Pattern authorFormat; 221 /** Specify the pattern for {@code @version} tag. */ 222 private Pattern versionFormat; 223 /** 224 * Control whether to ignore violations when a class has type parameters but 225 * does not have matching param tags in the Javadoc. 226 */ 227 private boolean allowMissingParamTags; 228 /** Control whether to ignore violations when a Javadoc tag is not recognised. */ 229 private boolean allowUnknownTags; 230 231 /** 232 * Specify the list of annotations that allow missed documentation. 233 * Only short names are allowed, e.g. {@code Generated}. 234 */ 235 private List<String> allowedAnnotations = Collections.singletonList("Generated"); 236 237 /** 238 * Setter to specify the visibility scope where Javadoc comments are checked. 239 * 240 * @param scope a scope. 241 */ 242 public void setScope(Scope scope) { 243 this.scope = scope; 244 } 245 246 /** 247 * Setter to specify the visibility scope where Javadoc comments are not checked. 248 * 249 * @param excludeScope a scope. 250 */ 251 public void setExcludeScope(Scope excludeScope) { 252 this.excludeScope = excludeScope; 253 } 254 255 /** 256 * Setter to specify the pattern for {@code @author} tag. 257 * 258 * @param pattern a pattern. 259 */ 260 public void setAuthorFormat(Pattern pattern) { 261 authorFormat = pattern; 262 } 263 264 /** 265 * Setter to specify the pattern for {@code @version} tag. 266 * 267 * @param pattern a pattern. 268 */ 269 public void setVersionFormat(Pattern pattern) { 270 versionFormat = pattern; 271 } 272 273 /** 274 * Setter to control whether to ignore violations when a class has type parameters but 275 * does not have matching param tags in the Javadoc. 276 * 277 * @param flag a {@code Boolean} value 278 */ 279 public void setAllowMissingParamTags(boolean flag) { 280 allowMissingParamTags = flag; 281 } 282 283 /** 284 * Setter to control whether to ignore violations when a Javadoc tag is not recognised. 285 * 286 * @param flag a {@code Boolean} value 287 */ 288 public void setAllowUnknownTags(boolean flag) { 289 allowUnknownTags = flag; 290 } 291 292 /** 293 * Setter to specify the list of annotations that allow missed documentation. 294 * Only short names are allowed, e.g. {@code Generated}. 295 * 296 * @param userAnnotations user's value. 297 */ 298 public void setAllowedAnnotations(String... userAnnotations) { 299 allowedAnnotations = Arrays.asList(userAnnotations); 300 } 301 302 @Override 303 public int[] getDefaultTokens() { 304 return getAcceptableTokens(); 305 } 306 307 @Override 308 public int[] getAcceptableTokens() { 309 return new int[] { 310 TokenTypes.INTERFACE_DEF, 311 TokenTypes.CLASS_DEF, 312 TokenTypes.ENUM_DEF, 313 TokenTypes.ANNOTATION_DEF, 314 }; 315 } 316 317 @Override 318 public int[] getRequiredTokens() { 319 return CommonUtil.EMPTY_INT_ARRAY; 320 } 321 322 @Override 323 public void visitToken(DetailAST ast) { 324 if (shouldCheck(ast)) { 325 final FileContents contents = getFileContents(); 326 final int lineNo = ast.getLineNo(); 327 final TextBlock textBlock = contents.getJavadocBefore(lineNo); 328 if (textBlock != null) { 329 final List<JavadocTag> tags = getJavadocTags(textBlock); 330 if (ScopeUtil.isOuterMostType(ast)) { 331 // don't check author/version for inner classes 332 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 333 authorFormat); 334 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 335 versionFormat); 336 } 337 338 final List<String> typeParamNames = 339 CheckUtil.getTypeParameterNames(ast); 340 341 if (!allowMissingParamTags) { 342 //Check type parameters that should exist, do 343 for (final String typeParamName : typeParamNames) { 344 checkTypeParamTag( 345 lineNo, tags, typeParamName); 346 } 347 } 348 349 checkUnusedTypeParamTags(tags, typeParamNames); 350 } 351 } 352 } 353 354 /** 355 * Whether we should check this node. 356 * @param ast a given node. 357 * @return whether we should check a given node. 358 */ 359 private boolean shouldCheck(final DetailAST ast) { 360 final Scope customScope; 361 362 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 363 customScope = Scope.PUBLIC; 364 } 365 else { 366 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 367 customScope = ScopeUtil.getScopeFromMods(mods); 368 } 369 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 370 371 return customScope.isIn(scope) 372 && (surroundingScope == null || surroundingScope.isIn(scope)) 373 && (excludeScope == null 374 || !customScope.isIn(excludeScope) 375 || surroundingScope != null 376 && !surroundingScope.isIn(excludeScope)) 377 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); 378 } 379 380 /** 381 * Gets all standalone tags from a given javadoc. 382 * @param textBlock the Javadoc comment to process. 383 * @return all standalone tags from the given javadoc. 384 */ 385 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 386 final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock, 387 JavadocUtil.JavadocTagType.BLOCK); 388 if (!allowUnknownTags) { 389 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 390 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 391 tag.getName()); 392 } 393 } 394 return tags.getValidTags(); 395 } 396 397 /** 398 * Verifies that a type definition has a required tag. 399 * @param lineNo the line number for the type definition. 400 * @param tags tags from the Javadoc comment for the type definition. 401 * @param tagName the required tag name. 402 * @param formatPattern regexp for the tag value. 403 */ 404 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 405 Pattern formatPattern) { 406 if (formatPattern != null) { 407 boolean hasTag = false; 408 final String tagPrefix = "@"; 409 for (int i = tags.size() - 1; i >= 0; i--) { 410 final JavadocTag tag = tags.get(i); 411 if (tag.getTagName().equals(tagName)) { 412 hasTag = true; 413 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 414 log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 415 } 416 } 417 } 418 if (!hasTag) { 419 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName); 420 } 421 } 422 } 423 424 /** 425 * Verifies that a type definition has the specified param tag for 426 * the specified type parameter name. 427 * @param lineNo the line number for the type definition. 428 * @param tags tags from the Javadoc comment for the type definition. 429 * @param typeParamName the name of the type parameter 430 */ 431 private void checkTypeParamTag(final int lineNo, 432 final List<JavadocTag> tags, final String typeParamName) { 433 boolean found = false; 434 for (int i = tags.size() - 1; i >= 0; i--) { 435 final JavadocTag tag = tags.get(i); 436 if (tag.isParamTag() 437 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET 438 + typeParamName + CLOSE_ANGLE_BRACKET) == 0) { 439 found = true; 440 break; 441 } 442 } 443 if (!found) { 444 log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 445 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 446 } 447 } 448 449 /** 450 * Checks for unused param tags for type parameters. 451 * @param tags tags from the Javadoc comment for the type definition. 452 * @param typeParamNames names of type parameters 453 */ 454 private void checkUnusedTypeParamTags( 455 final List<JavadocTag> tags, 456 final List<String> typeParamNames) { 457 for (int i = tags.size() - 1; i >= 0; i--) { 458 final JavadocTag tag = tags.get(i); 459 if (tag.isParamTag()) { 460 final String typeParamName = extractTypeParamNameFromTag(tag); 461 462 if (!typeParamNames.contains(typeParamName)) { 463 log(tag.getLineNo(), tag.getColumnNo(), 464 MSG_UNUSED_TAG, 465 JavadocTagInfo.PARAM.getText(), 466 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 467 } 468 } 469 } 470 } 471 472 /** 473 * Extracts type parameter name from tag. 474 * @param tag javadoc tag to extract parameter name 475 * @return extracts type parameter name from tag 476 */ 477 private static String extractTypeParamNameFromTag(JavadocTag tag) { 478 final String typeParamName; 479 final Matcher matchInAngleBrackets = 480 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 481 if (matchInAngleBrackets.find()) { 482 typeParamName = matchInAngleBrackets.group(1).trim(); 483 } 484 else { 485 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 486 } 487 return typeParamName; 488 } 489 490}