1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.javadoc; 21 22 import java.util.Set; 23 import java.util.regex.Matcher; 24 import java.util.regex.Pattern; 25 26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 28 import com.puppycrawl.tools.checkstyle.api.DetailAST; 29 import com.puppycrawl.tools.checkstyle.api.FileContents; 30 import com.puppycrawl.tools.checkstyle.api.Scope; 31 import com.puppycrawl.tools.checkstyle.api.TextBlock; 32 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 33 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 34 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 35 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 36 37 /** 38 * <p> 39 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is 40 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify 41 * another scope, set property scope to a different 42 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>. 43 * </p> 44 * <p> 45 * Javadoc is not required on a method that is tagged with the {@code @Override} annotation. 46 * However, under Java 5 it is not possible to mark a method required for an interface (this 47 * was <i>corrected</i> under Java 6). Hence, Checkstyle supports using the convention of using 48 * a single {@code {@inheritDoc}} tag instead of all the other tags. 49 * </p> 50 * <p> 51 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must 52 * match exactly the structures below. 53 * </p> 54 * <pre> 55 * public void setNumber(final int number) 56 * { 57 * mNumber = number; 58 * } 59 * 60 * public int getNumber() 61 * { 62 * return mNumber; 63 * } 64 * 65 * public boolean isSomething() 66 * { 67 * return false; 68 * } 69 * </pre> 70 * <ul> 71 * <li> 72 * Property {@code allowMissingPropertyJavadoc} - Control whether to allow missing Javadoc on 73 * accessor methods for properties (setters and getters). 74 * Type is {@code boolean}. 75 * Default value is {@code false}. 76 * </li> 77 * <li> 78 * Property {@code allowedAnnotations} - Configure annotations that allow missed 79 * documentation. 80 * Type is {@code java.lang.String[]}. 81 * Default value is {@code Override}. 82 * </li> 83 * <li> 84 * Property {@code excludeScope} - Specify the visibility scope where Javadoc comments are 85 * not checked. 86 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 87 * Default value is {@code null}. 88 * </li> 89 * <li> 90 * Property {@code ignoreMethodNamesRegex} - Ignore method whose names are matching specified 91 * regex. 92 * Type is {@code java.util.regex.Pattern}. 93 * Default value is {@code null}. 94 * </li> 95 * <li> 96 * Property {@code minLineCount} - Control the minimal amount of lines in method to allow no 97 * documentation. 98 * Type is {@code int}. 99 * Default value is {@code -1}. 100 * </li> 101 * <li> 102 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked. 103 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 104 * Default value is {@code public}. 105 * </li> 106 * <li> 107 * Property {@code tokens} - tokens to check 108 * Type is {@code java.lang.String[]}. 109 * Validation type is {@code tokenSet}. 110 * Default value is: 111 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 112 * METHOD_DEF</a>, 113 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 114 * CTOR_DEF</a>, 115 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 116 * ANNOTATION_FIELD_DEF</a>, 117 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 118 * COMPACT_CTOR_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.missing} 130 * </li> 131 * </ul> 132 * 133 * @since 8.21 134 */ 135 @FileStatefulCheck 136 public class MissingJavadocMethodCheck extends AbstractCheck { 137 138 /** 139 * A key is pointing to the warning message text in "messages.properties" 140 * file. 141 */ 142 public static final String MSG_JAVADOC_MISSING = "javadoc.missing"; 143 144 /** Maximum children allowed in setter/getter. */ 145 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 146 147 /** Pattern matching names of getter methods. */ 148 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 149 150 /** Pattern matching names of setter methods. */ 151 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 152 153 /** Maximum nodes allowed in a body of setter. */ 154 private static final int SETTER_BODY_SIZE = 3; 155 156 /** Default value of minimal amount of lines in method to allow no documentation.*/ 157 private static final int DEFAULT_MIN_LINE_COUNT = -1; 158 159 /** Specify the visibility scope where Javadoc comments are checked. */ 160 private Scope scope = Scope.PUBLIC; 161 162 /** Specify the visibility scope where Javadoc comments are not checked. */ 163 private Scope excludeScope; 164 165 /** Control the minimal amount of lines in method to allow no documentation.*/ 166 private int minLineCount = DEFAULT_MIN_LINE_COUNT; 167 168 /** 169 * Control whether to allow missing Javadoc on accessor methods for 170 * properties (setters and getters). 171 */ 172 private boolean allowMissingPropertyJavadoc; 173 174 /** Ignore method whose names are matching specified regex. */ 175 private Pattern ignoreMethodNamesRegex; 176 177 /** Configure annotations that allow missed documentation. */ 178 private Set<String> allowedAnnotations = Set.of("Override"); 179 180 /** 181 * Setter to configure annotations that allow missed documentation. 182 * 183 * @param userAnnotations user's value. 184 * @since 8.21 185 */ 186 public void setAllowedAnnotations(String... userAnnotations) { 187 allowedAnnotations = Set.of(userAnnotations); 188 } 189 190 /** 191 * Setter to ignore method whose names are matching specified regex. 192 * 193 * @param pattern a pattern. 194 * @since 8.21 195 */ 196 public void setIgnoreMethodNamesRegex(Pattern pattern) { 197 ignoreMethodNamesRegex = pattern; 198 } 199 200 /** 201 * Setter to control the minimal amount of lines in method to allow no documentation. 202 * 203 * @param value user's value. 204 * @since 8.21 205 */ 206 public void setMinLineCount(int value) { 207 minLineCount = value; 208 } 209 210 /** 211 * Setter to control whether to allow missing Javadoc on accessor methods for properties 212 * (setters and getters). 213 * 214 * @param flag a {@code Boolean} value 215 * @since 8.21 216 */ 217 public void setAllowMissingPropertyJavadoc(final boolean flag) { 218 allowMissingPropertyJavadoc = flag; 219 } 220 221 /** 222 * Setter to specify the visibility scope where Javadoc comments are checked. 223 * 224 * @param scope a scope. 225 * @since 8.21 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 8.21 236 */ 237 public void setExcludeScope(Scope excludeScope) { 238 this.excludeScope = excludeScope; 239 } 240 241 @Override 242 public final int[] getRequiredTokens() { 243 return CommonUtil.EMPTY_INT_ARRAY; 244 } 245 246 @Override 247 public int[] getDefaultTokens() { 248 return getAcceptableTokens(); 249 } 250 251 @Override 252 public int[] getAcceptableTokens() { 253 return new int[] { 254 TokenTypes.METHOD_DEF, 255 TokenTypes.CTOR_DEF, 256 TokenTypes.ANNOTATION_FIELD_DEF, 257 TokenTypes.COMPACT_CTOR_DEF, 258 }; 259 } 260 261 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 262 @SuppressWarnings("deprecation") 263 @Override 264 public final void visitToken(DetailAST ast) { 265 final Scope theScope = ScopeUtil.getScope(ast); 266 if (shouldCheck(ast, theScope)) { 267 final FileContents contents = getFileContents(); 268 final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo()); 269 270 if (textBlock == null && !isMissingJavadocAllowed(ast)) { 271 log(ast, MSG_JAVADOC_MISSING); 272 } 273 } 274 } 275 276 /** 277 * Some javadoc. 278 * 279 * @param methodDef Some javadoc. 280 * @return Some javadoc. 281 */ 282 private static int getMethodsNumberOfLine(DetailAST methodDef) { 283 final int numberOfLines; 284 final DetailAST lcurly = methodDef.getLastChild(); 285 final DetailAST rcurly = lcurly.getLastChild(); 286 287 if (lcurly.getFirstChild() == rcurly) { 288 numberOfLines = 1; 289 } 290 else { 291 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1; 292 } 293 return numberOfLines; 294 } 295 296 /** 297 * Checks if a missing Javadoc is allowed by the check's configuration. 298 * 299 * @param ast the tree node for the method or constructor. 300 * @return True if this method or constructor doesn't need Javadoc. 301 */ 302 private boolean isMissingJavadocAllowed(final DetailAST ast) { 303 return allowMissingPropertyJavadoc 304 && (isSetterMethod(ast) || isGetterMethod(ast)) 305 || matchesSkipRegex(ast) 306 || isContentsAllowMissingJavadoc(ast); 307 } 308 309 /** 310 * Checks if the Javadoc can be missing if the method or constructor is 311 * below the minimum line count or has a special annotation. 312 * 313 * @param ast the tree node for the method or constructor. 314 * @return True if this method or constructor doesn't need Javadoc. 315 */ 316 private boolean isContentsAllowMissingJavadoc(DetailAST ast) { 317 return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF 318 && (getMethodsNumberOfLine(ast) <= minLineCount 319 || AnnotationUtil.containsAnnotation(ast, allowedAnnotations)); 320 } 321 322 /** 323 * Checks if the given method name matches the regex. In that case 324 * we skip enforcement of javadoc for this method 325 * 326 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF} 327 * @return true if given method name matches the regex. 328 */ 329 private boolean matchesSkipRegex(DetailAST methodDef) { 330 boolean result = false; 331 if (ignoreMethodNamesRegex != null) { 332 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT); 333 final String methodName = ident.getText(); 334 335 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName); 336 if (matcher.matches()) { 337 result = true; 338 } 339 } 340 return result; 341 } 342 343 /** 344 * Whether we should check this node. 345 * 346 * @param ast a given node. 347 * @param nodeScope the scope of the node. 348 * @return whether we should check a given node. 349 */ 350 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) { 351 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 352 353 return nodeScope != excludeScope 354 && surroundingScope != excludeScope 355 && nodeScope.isIn(scope) 356 && surroundingScope.isIn(scope); 357 } 358 359 /** 360 * Returns whether an AST represents a getter method. 361 * 362 * @param ast the AST to check with 363 * @return whether the AST represents a getter method 364 */ 365 public static boolean isGetterMethod(final DetailAST ast) { 366 boolean getterMethod = false; 367 368 // Check have a method with exactly 7 children which are all that 369 // is allowed in a proper getter method which does not throw any 370 // exceptions. 371 if (ast.getType() == TokenTypes.METHOD_DEF 372 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 373 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 374 final String name = type.getNextSibling().getText(); 375 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 376 377 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 378 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 379 380 if (matchesGetterFormat && noParams) { 381 // Now verify that the body consists of: 382 // SLIST -> RETURN 383 // RCURLY 384 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 385 386 if (slist != null) { 387 final DetailAST expr = slist.getFirstChild(); 388 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 389 } 390 } 391 } 392 return getterMethod; 393 } 394 395 /** 396 * Returns whether an AST represents a setter method. 397 * 398 * @param ast the AST to check with 399 * @return whether the AST represents a setter method 400 */ 401 public static boolean isSetterMethod(final DetailAST ast) { 402 boolean setterMethod = false; 403 404 // Check have a method with exactly 7 children which are all that 405 // is allowed in a proper setter method which does not throw any 406 // exceptions. 407 if (ast.getType() == TokenTypes.METHOD_DEF 408 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) { 409 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 410 final String name = type.getNextSibling().getText(); 411 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 412 413 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 414 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 415 416 if (matchesSetterFormat && singleParam) { 417 // Now verify that the body consists of: 418 // SLIST -> EXPR -> ASSIGN 419 // SEMI 420 // RCURLY 421 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 422 423 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) { 424 final DetailAST expr = slist.getFirstChild(); 425 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 426 } 427 } 428 } 429 return setterMethod; 430 } 431 }