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