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 org.apache.commons.lang3.ArrayUtils; 027 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.FileContents; 031import com.puppycrawl.tools.checkstyle.api.Scope; 032import com.puppycrawl.tools.checkstyle.api.TextBlock; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 035import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 036import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 037import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 038 039/** 040 * Checks the Javadoc of a type. 041 * 042 * <p>Does not perform checks for author and version tags for inner classes, as 043 * they should be redundant because of outer class. 044 * 045 * @author Oliver Burn 046 * @author Michael Tamm 047 */ 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 authorFormatPattern; 107 /** Compiled regexp to match version tag content. **/ 108 private Pattern versionFormatPattern; 109 /** Regexp to match author tag content. */ 110 private String authorFormat; 111 /** Regexp to match version tag content. */ 112 private String versionFormat; 113 /** 114 * Controls whether to ignore errors when a method has type parameters but 115 * does not have matching param tags in the javadoc. Defaults to false. 116 */ 117 private boolean allowMissingParamTags; 118 /** Controls whether to flag errors for unknown tags. Defaults to false. */ 119 private boolean allowUnknownTags; 120 121 /** 122 * Sets the scope to check. 123 * @param from string to set scope from 124 */ 125 public void setScope(String from) { 126 scope = Scope.getInstance(from); 127 } 128 129 /** 130 * Set the excludeScope. 131 * @param excludeScope a {@code String} value 132 */ 133 public void setExcludeScope(String excludeScope) { 134 this.excludeScope = Scope.getInstance(excludeScope); 135 } 136 137 /** 138 * Set the author tag pattern. 139 * @param format a {@code String} value 140 */ 141 public void setAuthorFormat(String format) { 142 authorFormat = format; 143 authorFormatPattern = CommonUtils.createPattern(format); 144 } 145 146 /** 147 * Set the version format pattern. 148 * @param format a {@code String} value 149 */ 150 public void setVersionFormat(String format) { 151 versionFormat = format; 152 versionFormatPattern = CommonUtils.createPattern(format); 153 } 154 155 /** 156 * Controls whether to allow a type which has type parameters to 157 * omit matching param tags in the javadoc. Defaults to false. 158 * 159 * @param flag a {@code Boolean} value 160 */ 161 public void setAllowMissingParamTags(boolean flag) { 162 allowMissingParamTags = flag; 163 } 164 165 /** 166 * Controls whether to flag errors for unknown tags. Defaults to false. 167 * @param flag a {@code Boolean} value 168 */ 169 public void setAllowUnknownTags(boolean flag) { 170 allowUnknownTags = flag; 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 ArrayUtils.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 log(lineNo, MSG_JAVADOC_MISSING); 201 } 202 else { 203 final List<JavadocTag> tags = getJavadocTags(textBlock); 204 if (ScopeUtils.isOuterMostType(ast)) { 205 // don't check author/version for inner classes 206 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 207 authorFormatPattern, authorFormat); 208 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 209 versionFormatPattern, versionFormat); 210 } 211 212 final List<String> typeParamNames = 213 CheckUtils.getTypeParameterNames(ast); 214 215 if (!allowMissingParamTags) { 216 //Check type parameters that should exist, do 217 for (final String typeParamName : typeParamNames) { 218 checkTypeParamTag( 219 lineNo, tags, typeParamName); 220 } 221 } 222 223 checkUnusedTypeParamTags(tags, typeParamNames); 224 } 225 } 226 } 227 228 /** 229 * Whether we should check this node. 230 * @param ast a given node. 231 * @return whether we should check a given node. 232 */ 233 private boolean shouldCheck(final DetailAST ast) { 234 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 235 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 236 final Scope customScope; 237 238 if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) { 239 customScope = Scope.PUBLIC; 240 } 241 else { 242 customScope = declaredScope; 243 } 244 final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); 245 246 return customScope.isIn(scope) 247 && (surroundingScope == null || surroundingScope.isIn(scope)) 248 && (excludeScope == null 249 || !customScope.isIn(excludeScope) 250 || surroundingScope != null 251 && !surroundingScope.isIn(excludeScope)); 252 } 253 254 /** 255 * Gets all standalone tags from a given javadoc. 256 * @param textBlock the Javadoc comment to process. 257 * @return all standalone tags from the given javadoc. 258 */ 259 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 260 final JavadocTags tags = JavadocUtils.getJavadocTags(textBlock, 261 JavadocUtils.JavadocTagType.BLOCK); 262 if (!allowUnknownTags) { 263 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 264 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 265 tag.getName()); 266 } 267 } 268 return tags.getValidTags(); 269 } 270 271 /** 272 * Verifies that a type definition has a required tag. 273 * @param lineNo the line number for the type definition. 274 * @param tags tags from the Javadoc comment for the type definition. 275 * @param tagName the required tag name. 276 * @param formatPattern regexp for the tag value. 277 * @param format pattern for the tag value. 278 */ 279 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 280 Pattern formatPattern, String format) { 281 if (formatPattern == null) { 282 return; 283 } 284 285 int tagCount = 0; 286 final String tagPrefix = "@"; 287 for (int i = tags.size() - 1; i >= 0; i--) { 288 final JavadocTag tag = tags.get(i); 289 if (tag.getTagName().equals(tagName)) { 290 tagCount++; 291 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 292 log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, format); 293 } 294 } 295 } 296 if (tagCount == 0) { 297 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName); 298 } 299 } 300 301 /** 302 * Verifies that a type definition has the specified param tag for 303 * the specified type parameter name. 304 * @param lineNo the line number for the type definition. 305 * @param tags tags from the Javadoc comment for the type definition. 306 * @param typeParamName the name of the type parameter 307 */ 308 private void checkTypeParamTag(final int lineNo, 309 final List<JavadocTag> tags, final String typeParamName) { 310 boolean found = false; 311 for (int i = tags.size() - 1; i >= 0; i--) { 312 final JavadocTag tag = tags.get(i); 313 if (tag.isParamTag() 314 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET 315 + typeParamName + CLOSE_ANGLE_BRACKET) == 0) { 316 found = true; 317 } 318 } 319 if (!found) { 320 log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 321 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 322 } 323 } 324 325 /** 326 * Checks for unused param tags for type parameters. 327 * @param tags tags from the Javadoc comment for the type definition. 328 * @param typeParamNames names of type parameters 329 */ 330 private void checkUnusedTypeParamTags( 331 final List<JavadocTag> tags, 332 final List<String> typeParamNames) { 333 for (int i = tags.size() - 1; i >= 0; i--) { 334 final JavadocTag tag = tags.get(i); 335 if (tag.isParamTag()) { 336 337 final String typeParamName = extractTypeParamNameFromTag(tag); 338 339 if (!typeParamNames.contains(typeParamName)) { 340 log(tag.getLineNo(), tag.getColumnNo(), 341 MSG_UNUSED_TAG, 342 JavadocTagInfo.PARAM.getText(), 343 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 344 } 345 } 346 } 347 } 348 349 /** 350 * Extracts type parameter name from tag. 351 * @param tag javadoc tag to extract parameter name 352 * @return extracts type parameter name from tag 353 */ 354 private static String extractTypeParamNameFromTag(JavadocTag tag) { 355 final String typeParamName; 356 final Matcher matchInAngleBrackets = 357 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 358 if (matchInAngleBrackets.find()) { 359 typeParamName = matchInAngleBrackets.group(1).trim(); 360 } 361 else { 362 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 363 } 364 return typeParamName; 365 } 366}