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 * Setter to configure annotations that allow missed documentation. 121 * 122 * @param userAnnotations user's value. 123 * @since 8.21 124 */ 125 public void setAllowedAnnotations(String... userAnnotations) { 126 allowedAnnotations = Set.of(userAnnotations); 127 } 128 129 /** 130 * Setter to ignore method whose names are matching specified regex. 131 * 132 * @param pattern a pattern. 133 * @since 8.21 134 */ 135 public void setIgnoreMethodNamesRegex(Pattern pattern) { 136 ignoreMethodNamesRegex = pattern; 137 } 138 139 /** 140 * Setter to control the minimal amount of lines in method to allow no documentation. 141 * 142 * @param value user's value. 143 * @since 8.21 144 */ 145 public void setMinLineCount(int value) { 146 minLineCount = value; 147 } 148 149 /** 150 * Setter to control whether to allow missing Javadoc on accessor methods for properties 151 * (setters and getters). 152 * 153 * @param flag a {@code Boolean} value 154 * @since 8.21 155 */ 156 public void setAllowMissingPropertyJavadoc(final boolean flag) { 157 allowMissingPropertyJavadoc = flag; 158 } 159 160 /** 161 * Setter to specify the visibility scope where Javadoc comments are checked. 162 * 163 * @param scope a scope. 164 * @since 8.21 165 */ 166 public void setScope(Scope scope) { 167 this.scope = scope; 168 } 169 170 /** 171 * Setter to specify the visibility scope where Javadoc comments are not checked. 172 * 173 * @param excludeScope a scope. 174 * @since 8.21 175 */ 176 public void setExcludeScope(Scope excludeScope) { 177 this.excludeScope = excludeScope; 178 } 179 180 @Override 181 public final int[] getRequiredTokens() { 182 return CommonUtil.EMPTY_INT_ARRAY; 183 } 184 185 @Override 186 public int[] getDefaultTokens() { 187 return getAcceptableTokens(); 188 } 189 190 @Override 191 public int[] getAcceptableTokens() { 192 return new int[] { 193 TokenTypes.METHOD_DEF, 194 TokenTypes.CTOR_DEF, 195 TokenTypes.ANNOTATION_FIELD_DEF, 196 TokenTypes.COMPACT_CTOR_DEF, 197 }; 198 } 199 200 @Override 201 public boolean isCommentNodesRequired() { 202 return true; 203 } 204 205 @Override 206 public final void visitToken(DetailAST ast) { 207 final Scope theScope = ScopeUtil.getScope(ast); 208 if (shouldCheck(ast, theScope)) { 209 final DetailAST blockCommentNode = JavadocUtil.getAttachedJavadocComment(ast); 210 if (blockCommentNode == null && !isMissingJavadocAllowed(ast)) { 211 log(ast, MSG_JAVADOC_MISSING); 212 } 213 } 214 } 215 216 /** 217 * Some javadoc. 218 * 219 * @param methodDef Some javadoc. 220 * @return Some javadoc. 221 */ 222 private static int getMethodsNumberOfLine(DetailAST methodDef) { 223 int numberOfLines = 1; 224 final DetailAST lcurly = methodDef.getLastChild(); 225 final DetailAST rcurly = lcurly.getLastChild(); 226 if (rcurly != null && lcurly.getLineNo() != rcurly.getLineNo()) { 227 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1; 228 } 229 230 return numberOfLines; 231 } 232 233 /** 234 * Checks if a missing Javadoc is allowed by the check's configuration. 235 * 236 * @param ast the tree node for the method or constructor. 237 * @return True if this method or constructor doesn't need Javadoc. 238 */ 239 private boolean isMissingJavadocAllowed(final DetailAST ast) { 240 return allowMissingPropertyJavadoc 241 && (isSetterMethod(ast) || isGetterMethod(ast)) 242 || matchesSkipRegex(ast) 243 || isContentsAllowMissingJavadoc(ast); 244 } 245 246 /** 247 * Checks if the Javadoc can be missing if the method or constructor is 248 * below the minimum line count or has a special annotation. 249 * 250 * @param ast the tree node for the method or constructor. 251 * @return True if this method or constructor doesn't need Javadoc. 252 */ 253 private boolean isContentsAllowMissingJavadoc(DetailAST ast) { 254 return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF 255 && (getMethodsNumberOfLine(ast) <= minLineCount 256 || AnnotationUtil.containsAnnotation(ast, allowedAnnotations)); 257 } 258 259 /** 260 * Checks if the given method name matches the regex. In that case 261 * we skip enforcement of javadoc for this method 262 * 263 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF} 264 * @return true if given method name matches the regex. 265 */ 266 private boolean matchesSkipRegex(DetailAST methodDef) { 267 boolean result = false; 268 if (ignoreMethodNamesRegex != null) { 269 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT); 270 final String methodName = ident.getText(); 271 272 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName); 273 if (matcher.matches()) { 274 result = true; 275 } 276 } 277 return result; 278 } 279 280 /** 281 * Whether we should check this node. 282 * 283 * @param ast a given node. 284 * @param nodeScope the scope of the node. 285 * @return whether we should check a given node. 286 */ 287 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) { 288 return ScopeUtil.getSurroundingScope(ast) 289 .map(surroundingScope -> { 290 return nodeScope != excludeScope 291 && surroundingScope != excludeScope 292 && nodeScope.isIn(scope) 293 && surroundingScope.isIn(scope); 294 }) 295 .orElse(Boolean.FALSE); 296 } 297 298 /** 299 * Returns whether an AST represents a getter method. 300 * 301 * @param ast the AST to check with 302 * @return whether the AST represents a getter method 303 */ 304 public static boolean isGetterMethod(final DetailAST ast) { 305 boolean getterMethod = false; 306 307 // Check have a method with exactly 7 children which are all that 308 // is allowed in a proper getter method which does not throw any 309 // exceptions. 310 if (ast.getType() == TokenTypes.METHOD_DEF 311 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) { 312 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 313 final String name = type.getNextSibling().getText(); 314 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 315 316 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 317 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 318 319 if (matchesGetterFormat && noParams) { 320 // Now verify that the body consists of: 321 // SLIST -> RETURN 322 // RCURLY 323 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 324 325 if (slist != null) { 326 DetailAST expr = slist.getFirstChild(); 327 while (expr.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 328 expr = expr.getNextSibling(); 329 } 330 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN; 331 } 332 } 333 } 334 return getterMethod; 335 } 336 337 /** 338 * Returns whether an AST represents a setter method. 339 * 340 * @param ast the AST to check with 341 * @return whether the AST represents a setter method 342 */ 343 public static boolean isSetterMethod(final DetailAST ast) { 344 boolean setterMethod = false; 345 346 // Check have a method with exactly 7 children which are all that 347 // is allowed in a proper setter method which does not throw any 348 // exceptions. 349 if (ast.getType() == TokenTypes.METHOD_DEF 350 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) { 351 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 352 final String name = type.getNextSibling().getText(); 353 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches(); 354 355 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 356 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1; 357 358 if (matchesSetterFormat && singleParam) { 359 // Now verify that the body consists of: 360 // SLIST -> EXPR -> ASSIGN 361 // SEMI 362 // RCURLY 363 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 364 365 if (slist != null && getChildCount(slist) == SETTER_BODY_SIZE) { 366 final DetailAST expr = slist.getFirstChild(); 367 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN; 368 } 369 } 370 } 371 return setterMethod; 372 } 373 374 /** 375 * Returns the number of children without counting comments. 376 * 377 * @param detailAst parent ast 378 * @return the number of children 379 */ 380 private static int getChildCount(DetailAST detailAst) { 381 int childCount = 0; 382 DetailAST child = detailAst.getFirstChild(); 383 384 while (child != null) { 385 if (child.getType() != TokenTypes.SINGLE_LINE_COMMENT) { 386 childCount += 1; 387 } 388 child = child.getNextSibling(); 389 } 390 return childCount; 391 } 392}