001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.Arrays; 023import java.util.Collections; 024import java.util.List; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028import com.puppycrawl.tools.checkstyle.StatelessCheck; 029import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 030import com.puppycrawl.tools.checkstyle.api.DetailAST; 031import com.puppycrawl.tools.checkstyle.api.FileContents; 032import com.puppycrawl.tools.checkstyle.api.Scope; 033import com.puppycrawl.tools.checkstyle.api.TextBlock; 034import com.puppycrawl.tools.checkstyle.api.TokenTypes; 035import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 036import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 037import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 038import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 039import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 040 041/** 042 * Checks the Javadoc of a type. 043 * 044 * <p>Does not perform checks for author and version tags for inner classes, as 045 * they should be redundant because of outer class. 046 * 047 */ 048@StatelessCheck 049public class JavadocTypeCheck 050 extends AbstractCheck { 051 052 /** 053 * A key is pointing to the warning message text in "messages.properties" 054 * file. 055 */ 056 public static final String MSG_JAVADOC_MISSING = "javadoc.missing"; 057 058 /** 059 * A key is pointing to the warning message text in "messages.properties" 060 * file. 061 */ 062 public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag"; 063 064 /** 065 * A key is pointing to the warning message text in "messages.properties" 066 * file. 067 */ 068 public static final String MSG_TAG_FORMAT = "type.tagFormat"; 069 070 /** 071 * A key is pointing to the warning message text in "messages.properties" 072 * file. 073 */ 074 public static final String MSG_MISSING_TAG = "type.missingTag"; 075 076 /** 077 * A key is pointing to the warning message text in "messages.properties" 078 * file. 079 */ 080 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag"; 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral"; 087 088 /** Open angle bracket literal. */ 089 private static final String OPEN_ANGLE_BRACKET = "<"; 090 091 /** Close angle bracket literal. */ 092 private static final String CLOSE_ANGLE_BRACKET = ">"; 093 094 /** Pattern to match type name within angle brackets in javadoc param tag. */ 095 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG = 096 Pattern.compile("\\s*<([^>]+)>.*"); 097 098 /** Pattern to split type name field in javadoc param tag. */ 099 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER = 100 Pattern.compile("\\s+"); 101 102 /** The scope to check for. */ 103 private Scope scope = Scope.PRIVATE; 104 /** The visibility scope where Javadoc comments shouldn't be checked. **/ 105 private Scope excludeScope; 106 /** Compiled regexp to match author tag content. **/ 107 private Pattern authorFormat; 108 /** Compiled regexp to match version tag content. **/ 109 private Pattern versionFormat; 110 /** 111 * Controls whether to ignore errors when a method has type parameters but 112 * does not have matching param tags in the javadoc. Defaults to false. 113 */ 114 private boolean allowMissingParamTags; 115 /** Controls whether to flag errors for unknown tags. Defaults to false. */ 116 private boolean allowUnknownTags; 117 118 /** List of annotations that allow missed documentation. */ 119 private List<String> allowedAnnotations = Collections.singletonList("Generated"); 120 121 /** 122 * Sets the scope to check. 123 * @param scope a scope. 124 */ 125 public void setScope(Scope scope) { 126 this.scope = scope; 127 } 128 129 /** 130 * Set the excludeScope. 131 * @param excludeScope a scope. 132 */ 133 public void setExcludeScope(Scope excludeScope) { 134 this.excludeScope = excludeScope; 135 } 136 137 /** 138 * Set the author tag pattern. 139 * @param pattern a pattern. 140 */ 141 public void setAuthorFormat(Pattern pattern) { 142 authorFormat = pattern; 143 } 144 145 /** 146 * Set the version format pattern. 147 * @param pattern a pattern. 148 */ 149 public void setVersionFormat(Pattern pattern) { 150 versionFormat = pattern; 151 } 152 153 /** 154 * Controls whether to allow a type which has type parameters to 155 * omit matching param tags in the javadoc. Defaults to false. 156 * 157 * @param flag a {@code Boolean} value 158 */ 159 public void setAllowMissingParamTags(boolean flag) { 160 allowMissingParamTags = flag; 161 } 162 163 /** 164 * Controls whether to flag errors for unknown tags. Defaults to false. 165 * @param flag a {@code Boolean} value 166 */ 167 public void setAllowUnknownTags(boolean flag) { 168 allowUnknownTags = flag; 169 } 170 171 /** 172 * Sets list of annotations. 173 * @param userAnnotations user's value. 174 */ 175 public void setAllowedAnnotations(String... userAnnotations) { 176 allowedAnnotations = Arrays.asList(userAnnotations); 177 } 178 179 @Override 180 public int[] getDefaultTokens() { 181 return getAcceptableTokens(); 182 } 183 184 @Override 185 public int[] getAcceptableTokens() { 186 return new int[] { 187 TokenTypes.INTERFACE_DEF, 188 TokenTypes.CLASS_DEF, 189 TokenTypes.ENUM_DEF, 190 TokenTypes.ANNOTATION_DEF, 191 }; 192 } 193 194 @Override 195 public int[] getRequiredTokens() { 196 return CommonUtil.EMPTY_INT_ARRAY; 197 } 198 199 @Override 200 public void visitToken(DetailAST ast) { 201 if (shouldCheck(ast)) { 202 final FileContents contents = getFileContents(); 203 final int lineNo = ast.getLineNo(); 204 final TextBlock textBlock = contents.getJavadocBefore(lineNo); 205 if (textBlock == null) { 206 log(lineNo, MSG_JAVADOC_MISSING); 207 } 208 else { 209 final List<JavadocTag> tags = getJavadocTags(textBlock); 210 if (ScopeUtil.isOuterMostType(ast)) { 211 // don't check author/version for inner classes 212 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 213 authorFormat); 214 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 215 versionFormat); 216 } 217 218 final List<String> typeParamNames = 219 CheckUtil.getTypeParameterNames(ast); 220 221 if (!allowMissingParamTags) { 222 //Check type parameters that should exist, do 223 for (final String typeParamName : typeParamNames) { 224 checkTypeParamTag( 225 lineNo, tags, typeParamName); 226 } 227 } 228 229 checkUnusedTypeParamTags(tags, typeParamNames); 230 } 231 } 232 } 233 234 /** 235 * Whether we should check this node. 236 * @param ast a given node. 237 * @return whether we should check a given node. 238 */ 239 private boolean shouldCheck(final DetailAST ast) { 240 final Scope customScope; 241 242 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) { 243 customScope = Scope.PUBLIC; 244 } 245 else { 246 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 247 customScope = ScopeUtil.getScopeFromMods(mods); 248 } 249 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 250 251 return customScope.isIn(scope) 252 && (surroundingScope == null || surroundingScope.isIn(scope)) 253 && (excludeScope == null 254 || !customScope.isIn(excludeScope) 255 || surroundingScope != null 256 && !surroundingScope.isIn(excludeScope)) 257 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); 258 } 259 260 /** 261 * Gets all standalone tags from a given javadoc. 262 * @param textBlock the Javadoc comment to process. 263 * @return all standalone tags from the given javadoc. 264 */ 265 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 266 final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock, 267 JavadocUtil.JavadocTagType.BLOCK); 268 if (!allowUnknownTags) { 269 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 270 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 271 tag.getName()); 272 } 273 } 274 return tags.getValidTags(); 275 } 276 277 /** 278 * Verifies that a type definition has a required tag. 279 * @param lineNo the line number for the type definition. 280 * @param tags tags from the Javadoc comment for the type definition. 281 * @param tagName the required tag name. 282 * @param formatPattern regexp for the tag value. 283 */ 284 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 285 Pattern formatPattern) { 286 if (formatPattern != null) { 287 boolean hasTag = false; 288 final String tagPrefix = "@"; 289 for (int i = tags.size() - 1; i >= 0; i--) { 290 final JavadocTag tag = tags.get(i); 291 if (tag.getTagName().equals(tagName)) { 292 hasTag = true; 293 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 294 log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 295 } 296 } 297 } 298 if (!hasTag) { 299 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName); 300 } 301 } 302 } 303 304 /** 305 * Verifies that a type definition has the specified param tag for 306 * the specified type parameter name. 307 * @param lineNo the line number for the type definition. 308 * @param tags tags from the Javadoc comment for the type definition. 309 * @param typeParamName the name of the type parameter 310 */ 311 private void checkTypeParamTag(final int lineNo, 312 final List<JavadocTag> tags, final String typeParamName) { 313 boolean found = false; 314 for (int i = tags.size() - 1; i >= 0; i--) { 315 final JavadocTag tag = tags.get(i); 316 if (tag.isParamTag() 317 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET 318 + typeParamName + CLOSE_ANGLE_BRACKET) == 0) { 319 found = true; 320 break; 321 } 322 } 323 if (!found) { 324 log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 325 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 326 } 327 } 328 329 /** 330 * Checks for unused param tags for type parameters. 331 * @param tags tags from the Javadoc comment for the type definition. 332 * @param typeParamNames names of type parameters 333 */ 334 private void checkUnusedTypeParamTags( 335 final List<JavadocTag> tags, 336 final List<String> typeParamNames) { 337 for (int i = tags.size() - 1; i >= 0; i--) { 338 final JavadocTag tag = tags.get(i); 339 if (tag.isParamTag()) { 340 final String typeParamName = extractTypeParamNameFromTag(tag); 341 342 if (!typeParamNames.contains(typeParamName)) { 343 log(tag.getLineNo(), tag.getColumnNo(), 344 MSG_UNUSED_TAG, 345 JavadocTagInfo.PARAM.getText(), 346 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 347 } 348 } 349 } 350 } 351 352 /** 353 * Extracts type parameter name from tag. 354 * @param tag javadoc tag to extract parameter name 355 * @return extracts type parameter name from tag 356 */ 357 private static String extractTypeParamNameFromTag(JavadocTag tag) { 358 final String typeParamName; 359 final Matcher matchInAngleBrackets = 360 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 361 if (matchInAngleBrackets.find()) { 362 typeParamName = matchInAngleBrackets.group(1).trim(); 363 } 364 else { 365 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 366 } 367 return typeParamName; 368 } 369 370}