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.StatelessCheck; 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 * {@snippet lang="text" : 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 * } 072 * 073 * @since 8.21 074 */ 075@StatelessCheck 076public class MissingJavadocMethodCheck extends AbstractCheck { 077 078 /** 079 * A key is pointing to the warning message text in "messages.properties" file. 080 */ 081 public static final String MSG_JAVADOC_MISSING = "javadoc.missing.named"; 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 final String name = NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT)) 219 .getText(); 220 log(ast, MSG_JAVADOC_MISSING, name); 221 } 222 } 223 } 224 225 /** 226 * Some javadoc. 227 * 228 * @param methodDef Some javadoc. 229 * @return Some javadoc. 230 */ 231 private static int getMethodsNumberOfLine(DetailAST methodDef) { 232 int numberOfLines = 1; 233 final DetailAST lcurly = methodDef.getLastChild(); 234 final DetailAST rcurly = lcurly.getLastChild(); 235 if (rcurly != null && lcurly.getLineNo() != rcurly.getLineNo()) { 236 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1; 237 } 238 239 return numberOfLines; 240 } 241 242 /** 243 * Checks if a missing Javadoc is allowed by the check's configuration. 244 * 245 * @param ast the tree node for the method or constructor. 246 * @return True if this method or constructor doesn't need Javadoc. 247 */ 248 private boolean isMissingJavadocAllowed(final DetailAST ast) { 249 return allowMissingPropertyJavadoc 250 && (isSetterMethod(ast) || isGetterMethod(ast)) 251 || matchesSkipRegex(ast) 252 || isContentsAllowMissingJavadoc(ast); 253 } 254 255 /** 256 * Checks if the Javadoc can be missing if the method or constructor is 257 * below the minimum line count or has a special annotation. 258 * 259 * @param ast the tree node for the method or constructor. 260 * @return True if this method or constructor doesn't need Javadoc. 261 */ 262 private boolean isContentsAllowMissingJavadoc(DetailAST ast) { 263 return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF 264 && (getMethodsNumberOfLine(ast) <= minLineCount 265 || AnnotationUtil.containsAnnotation(ast, allowedAnnotations)); 266 } 267 268 /** 269 * Checks if the given method name matches the regex. In that case 270 * we skip enforcement of javadoc for this method 271 * 272 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF} 273 * @return true if given method name matches the regex. 274 */ 275 private boolean matchesSkipRegex(DetailAST methodDef) { 276 boolean result = false; 277 if (ignoreMethodNamesRegex != null) { 278 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT); 279 final String methodName = ident.getText(); 280 281 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName); 282 if (matcher.matches()) { 283 result = true; 284 } 285 } 286 return result; 287 } 288 289 /** 290 * Whether we should check this node. 291 * 292 * @param ast a given node. 293 * @param nodeScope the scope of the node. 294 * @return whether we should check a given node. 295 */ 296 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) { 297 return ScopeUtil.getSurroundingScope(ast) 298 .map(surroundingScope -> { 299 return nodeScope != excludeScope 300 && surroundingScope != excludeScope 301 && nodeScope.isIn(scope) 302 && surroundingScope.isIn(scope); 303 }) 304 .orElse(Boolean.FALSE); 305 } 306 307 /** 308 * Returns whether an AST represents a getter method. 309 * 310 * @param ast the AST to check with 311 * @return whether the AST represents a getter method 312 */ 313 public static boolean isGetterMethod(final DetailAST ast) { 314 boolean getterMethod = false; 315 316 // Check have a method with exactly 7 children which are all that 317 // is allowed in a proper getter method which does not throw any 318 // exceptions. 319 if (ast.getType() == TokenTypes.METHOD_DEF 320 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) { 321 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 322 final String name = type.getNextSibling().getText(); 323 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches(); 324 325 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 326 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0; 327 328 if (matchesGetterFormat && noParams) { 329 // Now verify that the body consists of: 330 // SLIST -> RETURN 331 // RCURLY 332 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 333 334 if (slist != null) { 335 DetailAST expr = slist.getFirstChild(); 336 while (expr.getType() == TokenTypes.SINGLE_LINE_COMMENT 337 || expr.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) { 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 && child.getType() != TokenTypes.BLOCK_COMMENT_BEGIN) { 397 childCount += 1; 398 } 399 child = child.getNextSibling(); 400 } 401 return childCount; 402 } 403 404}