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