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