001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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.Set; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.FileContents; 030import com.puppycrawl.tools.checkstyle.api.Scope; 031import com.puppycrawl.tools.checkstyle.api.TextBlock; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 034import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 035import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 036 037/** 038 * <div> 039 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is 040 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify 041 * another scope, set property scope to a different 042 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>. 043 * </div> 044 * 045 * <p> 046 * Javadoc is not required on a method that is tagged with the {@code @Override} annotation. 047 * However, under Java 5 it is not possible to mark a method required for an interface (this 048 * was <i>corrected</i> under Java 6). Hence, Checkstyle supports using the convention of using 049 * a single {@code {@inheritDoc}} tag instead of all the other tags. 050 * </p> 051 * 052 * <p> 053 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must 054 * match exactly the structures below. 055 * </p> 056 * <pre> 057 * public void setNumber(final int number) 058 * { 059 * mNumber = number; 060 * } 061 * 062 * public int getNumber() 063 * { 064 * return mNumber; 065 * } 066 * 067 * public boolean isSomething() 068 * { 069 * return false; 070 * } 071 * </pre> 072 * <ul> 073 * <li> 074 * Property {@code allowMissingPropertyJavadoc} - Control whether to allow missing Javadoc on 075 * accessor methods for properties (setters and getters). 076 * Type is {@code boolean}. 077 * Default value is {@code false}. 078 * </li> 079 * <li> 080 * Property {@code allowedAnnotations} - Configure annotations that allow missed 081 * documentation. 082 * Type is {@code java.lang.String[]}. 083 * Default value is {@code Override}. 084 * </li> 085 * <li> 086 * Property {@code excludeScope} - Specify the visibility scope where Javadoc comments are 087 * not checked. 088 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 089 * Default value is {@code null}. 090 * </li> 091 * <li> 092 * Property {@code ignoreMethodNamesRegex} - Ignore method whose names are matching specified 093 * regex. 094 * Type is {@code java.util.regex.Pattern}. 095 * Default value is {@code null}. 096 * </li> 097 * <li> 098 * Property {@code minLineCount} - Control the minimal amount of lines in method to allow no 099 * 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 140public 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}