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