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