001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.Scope; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 033import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 034import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 035 036/** 037 * <div> 038 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is 039 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify 040 * another scope, set property scope to a different 041 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>. 042 * </div> 043 * 044 * <p> 045 * Javadoc is not required on a method that is tagged with the {@code @Override} annotation. 046 * However, under Java 5 it is not possible to mark a method required for an interface (this 047 * was <i>corrected</i> under Java 6). Hence, Checkstyle supports using the convention of using 048 * a single {@code {@inheritDoc}} tag instead of all the other tags. 049 * </p> 050 * 051 * <p> 052 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must 053 * match exactly the structures below. 054 * </p> 055 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 056 * public void setNumber(final int number) 057 * { 058 * mNumber = number; 059 * } 060 * 061 * public int getNumber() 062 * { 063 * return mNumber; 064 * } 065 * 066 * public boolean isSomething() 067 * { 068 * return false; 069 * } 070 * </code></pre></div> 071 * 072 * @since 8.21 073 */ 074@FileStatefulCheck 075public class MissingJavadocMethodCheck extends AbstractCheck { 076 077 /** 078 * A key is pointing to the warning message text in "messages.properties" 079 * file. 080 */ 081 public static final String MSG_JAVADOC_MISSING = "javadoc.missing"; 082 083 /** Maximum children allowed in setter/getter. */ 084 private static final int SETTER_GETTER_MAX_CHILDREN = 7; 085 086 /** Pattern matching names of getter methods. */ 087 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*"); 088 089 /** Pattern matching names of setter methods. */ 090 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*"); 091 092 /** Maximum nodes allowed in a body of setter. */ 093 private static final int SETTER_BODY_SIZE = 3; 094 095 /** Default value of minimal amount of lines in method to allow no documentation.*/ 096 private static final int DEFAULT_MIN_LINE_COUNT = -1; 097 098 /** Specify the visibility scope where Javadoc comments are checked. */ 099 private Scope scope = Scope.PUBLIC; 100 101 /** Specify the visibility scope where Javadoc comments are not checked. */ 102 private Scope excludeScope; 103 104 /** Control the minimal amount of lines in method to allow no documentation.*/ 105 private int minLineCount = DEFAULT_MIN_LINE_COUNT; 106 107 /** 108 * Control whether to allow missing Javadoc on accessor methods for 109 * properties (setters and getters). 110 */ 111 private boolean allowMissingPropertyJavadoc; 112 113 /** Ignore method whose names are matching specified regex. */ 114 private Pattern ignoreMethodNamesRegex; 115 116 /** Configure annotations that allow missed documentation. */ 117 private Set<String> allowedAnnotations = Set.of("Override"); 118 119 /** 120 * Creates a new {@code MissingJavadocMethodCheck} instance. 121 */ 122 public MissingJavadocMethodCheck() { 123 // no code by default 124 } 125 126 /** 127 * Setter to configure annotations that allow missed documentation. 128 * 129 * @param userAnnotations user's value. 130 * @since 8.21 131 */ 132 public void setAllowedAnnotations(String... userAnnotations) { 133 allowedAnnotations = Set.of(userAnnotations); 134 } 135 136 /** 137 * Setter to ignore method whose names are matching specified regex. 138 * 139 * @param pattern a pattern. 140 * @since 8.21 141 */ 142 public void setIgnoreMethodNamesRegex(Pattern pattern) { 143 ignoreMethodNamesRegex = pattern; 144 } 145 146 /** 147 * Setter to control the minimal amount of lines in method to allow no documentation. 148 * 149 * @param value user's value. 150 * @since 8.21 151 */ 152 public void setMinLineCount(int value) { 153 minLineCount = value; 154 } 155 156 /** 157 * Setter to control whether to allow missing Javadoc on accessor methods for properties 158 * (setters and getters). 159 * 160 * @param flag a {@code Boolean} value 161 * @since 8.21 162 */ 163 public void setAllowMissingPropertyJavadoc(final boolean flag) { 164 allowMissingPropertyJavadoc = flag; 165 } 166 167 /** 168 * Setter to specify the visibility scope where Javadoc comments are checked. 169 * 170 * @param scope a scope. 171 * @since 8.21 172 */ 173 public void setScope(Scope scope) { 174 this.scope = scope; 175 } 176 177 /** 178 * Setter to specify the visibility scope where Javadoc comments are not checked. 179 * 180 * @param excludeScope a scope. 181 * @since 8.21 182 */ 183 public void setExcludeScope(Scope excludeScope) { 184 this.excludeScope = excludeScope; 185 } 186 187 @Override 188 public final int[] getRequiredTokens() { 189 return CommonUtil.EMPTY_INT_ARRAY; 190 } 191 192 @Override 193 public int[] getDefaultTokens() { 194 return getAcceptableTokens(); 195 } 196 197 @Override 198 public int[] getAcceptableTokens() { 199 return new int[] { 200 TokenTypes.METHOD_DEF, 201 TokenTypes.CTOR_DEF, 202 TokenTypes.ANNOTATION_FIELD_DEF, 203 TokenTypes.COMPACT_CTOR_DEF, 204 }; 205 } 206 207 @Override 208 public boolean isCommentNodesRequired() { 209 return true; 210 } 211 212 @Override 213 public final void visitToken(DetailAST ast) { 214 final Scope theScope = ScopeUtil.getScope(ast); 215 if (shouldCheck(ast, theScope)) { 216 final DetailAST blockCommentNode = JavadocUtil.getAttachedJavadocComment(ast); 217 if (blockCommentNode == null && !isMissingJavadocAllowed(ast)) { 218 log(ast, MSG_JAVADOC_MISSING); 219 } 220 } 221 } 222 223 /** 224 * Some javadoc. 225 * 226 * @param methodDef Some javadoc. 227 * @return Some javadoc. 228 */ 229 private static int getMethodsNumberOfLine(DetailAST methodDef) { 230 int numberOfLines = 1; 231 final DetailAST lcurly = methodDef.getLastChild(); 232 final DetailAST rcurly = lcurly.getLastChild(); 233 if (rcurly != null && lcurly.getLineNo() != rcurly.getLineNo()) { 234 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1; 235 } 236 237 return numberOfLines; 238 } 239 240 /** 241 * Checks if a missing Javadoc is allowed by the check's configuration. 242 * 243 * @param ast the tree node for the method or constructor. 244 * @return True if this method or constructor doesn't need Javadoc. 245 */ 246 private boolean isMissingJavadocAllowed(final DetailAST ast) { 247 return allowMissingPropertyJavadoc 248 && (isSetterMethod(ast) || isGetterMethod(ast)) 249 || matchesSkipRegex(ast) 250 || isContentsAllowMissingJavadoc(ast); 251 } 252 253 /** 254 * Checks if the Javadoc can be missing if the method or constructor is 255 * below the minimum line count or has a special annotation. 256 * 257 * @param ast the tree node for the method or constructor. 258 * @return True if this method or constructor doesn't need Javadoc. 259 */ 260 private boolean isContentsAllowMissingJavadoc(DetailAST ast) { 261 return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF 262 && (getMethodsNumberOfLine(ast) <= minLineCount 263 || AnnotationUtil.containsAnnotation(ast, allowedAnnotations)); 264 } 265 266 /** 267 * Checks if the given method name matches the regex. In that case 268 * we skip enforcement of javadoc for this method 269 * 270 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF} 271 * @return true if given method name matches the regex. 272 */ 273 private boolean matchesSkipRegex(DetailAST methodDef) { 274 boolean result = false; 275 if (ignoreMethodNamesRegex != null) { 276 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT); 277 final String methodName = ident.getText(); 278 279 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName); 280 if (matcher.matches()) { 281 result = true; 282 } 283 } 284 return result; 285 } 286 287 /** 288 * Whether we should check this node. 289 * 290 * @param ast a given node. 291 * @param nodeScope the scope of the node. 292 * @return whether we should check a given node. 293 */ 294 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) { 295 return ScopeUtil.getSurroundingScope(ast) 296 .map(surroundingScope -> { 297 return nodeScope != excludeScope 298 && surroundingScope != excludeScope 299 && nodeScope.isIn(scope) 300 && surroundingScope.isIn(scope); 301 }) 302 .orElse(Boolean.FALSE); 303 } 304 305 /** 306 * Returns whether an AST represents a getter method. 307 * 308 * @param ast the AST to check with 309 * @return whether the AST represents a getter method 310 */ 311 public static boolean isGetterMethod(final DetailAST ast) { 312 boolean getterMethod = false; 313 314 // Check have a method with exactly 7 children which are all that 315 // is allowed in a proper getter method which does not throw any 316 // exceptions. 317 if (ast.getType() == TokenTypes.METHOD_DEF 318 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) { 319 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 320 final String name = type.getNextSibling().getText(); 321 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 322 323 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 324 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 325 326 if (matchesGetterFormat && noParams) { 327 // Now verify that the body consists of: 328 // SLIST -> RETURN 329 // RCURLY 330 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 331 332 if (slist != null) { 333 DetailAST expr = slist.getFirstChild(); 334 while (expr.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 335 expr = expr.getNextSibling(); 336 } 337 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 338 } 339 } 340 } 341 return getterMethod; 342 } 343 344 /** 345 * Returns whether an AST represents a setter method. 346 * 347 * @param ast the AST to check with 348 * @return whether the AST represents a setter method 349 */ 350 public static boolean isSetterMethod(final DetailAST ast) { 351 boolean setterMethod = false; 352 353 // Check have a method with exactly 7 children which are all that 354 // is allowed in a proper setter method which does not throw any 355 // exceptions. 356 if (ast.getType() == TokenTypes.METHOD_DEF 357 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) { 358 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 359 final String name = type.getNextSibling().getText(); 360 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 361 362 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 363 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 364 365 if (matchesSetterFormat && singleParam) { 366 // Now verify that the body consists of: 367 // SLIST -> EXPR -> ASSIGN 368 // SEMI 369 // RCURLY 370 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 371 372 if (slist != null && getChildCount(slist) == SETTER_BODY_SIZE) { 373 final DetailAST expr = slist.getFirstChild(); 374 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 375 } 376 } 377 } 378 return setterMethod; 379 } 380 381 /** 382 * Returns the number of children without counting comments. 383 * 384 * @param detailAst parent ast 385 * @return the number of children 386 */ 387 private static int getChildCount(DetailAST detailAst) { 388 int childCount = 0; 389 DetailAST child = detailAst.getFirstChild(); 390 391 while (child != null) { 392 if (child.getType() != TokenTypes.SINGLE_LINE_COMMENT) { 393 childCount += 1; 394 } 395 child = child.getNextSibling(); 396 } 397 return childCount; 398 } 399 400}