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