001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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 authorFormat; 105 /** Compiled regexp to match version tag content. **/ 106 private Pattern versionFormat; 107 /** 108 * Controls whether to ignore errors when a method has type parameters but 109 * does not have matching param tags in the javadoc. Defaults to false. 110 */ 111 private boolean allowMissingParamTags; 112 /** Controls whether to flag errors for unknown tags. Defaults to false. */ 113 private boolean allowUnknownTags; 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 errors 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 @Override 166 public int[] getDefaultTokens() { 167 return getAcceptableTokens(); 168 } 169 170 @Override 171 public int[] getAcceptableTokens() { 172 return new int[] { 173 TokenTypes.INTERFACE_DEF, 174 TokenTypes.CLASS_DEF, 175 TokenTypes.ENUM_DEF, 176 TokenTypes.ANNOTATION_DEF, 177 }; 178 } 179 180 @Override 181 public int[] getRequiredTokens() { 182 return CommonUtils.EMPTY_INT_ARRAY; 183 } 184 185 @Override 186 public void visitToken(DetailAST ast) { 187 if (shouldCheck(ast)) { 188 final FileContents contents = getFileContents(); 189 final int lineNo = ast.getLineNo(); 190 final TextBlock textBlock = contents.getJavadocBefore(lineNo); 191 if (textBlock == null) { 192 log(lineNo, MSG_JAVADOC_MISSING); 193 } 194 else { 195 final List<JavadocTag> tags = getJavadocTags(textBlock); 196 if (ScopeUtils.isOuterMostType(ast)) { 197 // don't check author/version for inner classes 198 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 199 authorFormat); 200 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 201 versionFormat); 202 } 203 204 final List<String> typeParamNames = 205 CheckUtils.getTypeParameterNames(ast); 206 207 if (!allowMissingParamTags) { 208 //Check type parameters that should exist, do 209 for (final String typeParamName : typeParamNames) { 210 checkTypeParamTag( 211 lineNo, tags, typeParamName); 212 } 213 } 214 215 checkUnusedTypeParamTags(tags, typeParamNames); 216 } 217 } 218 } 219 220 /** 221 * Whether we should check this node. 222 * @param ast a given node. 223 * @return whether we should check a given node. 224 */ 225 private boolean shouldCheck(final DetailAST ast) { 226 final Scope customScope; 227 228 if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) { 229 customScope = Scope.PUBLIC; 230 } 231 else { 232 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 233 customScope = ScopeUtils.getScopeFromMods(mods); 234 } 235 final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); 236 237 return customScope.isIn(scope) 238 && (surroundingScope == null || surroundingScope.isIn(scope)) 239 && (excludeScope == null 240 || !customScope.isIn(excludeScope) 241 || surroundingScope != null 242 && !surroundingScope.isIn(excludeScope)); 243 } 244 245 /** 246 * Gets all standalone tags from a given javadoc. 247 * @param textBlock the Javadoc comment to process. 248 * @return all standalone tags from the given javadoc. 249 */ 250 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 251 final JavadocTags tags = JavadocUtils.getJavadocTags(textBlock, 252 JavadocUtils.JavadocTagType.BLOCK); 253 if (!allowUnknownTags) { 254 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 255 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, 256 tag.getName()); 257 } 258 } 259 return tags.getValidTags(); 260 } 261 262 /** 263 * Verifies that a type definition has a required tag. 264 * @param lineNo the line number for the type definition. 265 * @param tags tags from the Javadoc comment for the type definition. 266 * @param tagName the required tag name. 267 * @param formatPattern regexp for the tag value. 268 */ 269 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 270 Pattern formatPattern) { 271 if (formatPattern != null) { 272 int tagCount = 0; 273 final String tagPrefix = "@"; 274 for (int i = tags.size() - 1; i >= 0; i--) { 275 final JavadocTag tag = tags.get(i); 276 if (tag.getTagName().equals(tagName)) { 277 tagCount++; 278 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 279 log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); 280 } 281 } 282 } 283 if (tagCount == 0) { 284 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName); 285 } 286 } 287 } 288 289 /** 290 * Verifies that a type definition has the specified param tag for 291 * the specified type parameter name. 292 * @param lineNo the line number for the type definition. 293 * @param tags tags from the Javadoc comment for the type definition. 294 * @param typeParamName the name of the type parameter 295 */ 296 private void checkTypeParamTag(final int lineNo, 297 final List<JavadocTag> tags, final String typeParamName) { 298 boolean found = false; 299 for (int i = tags.size() - 1; i >= 0; i--) { 300 final JavadocTag tag = tags.get(i); 301 if (tag.isParamTag() 302 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET 303 + typeParamName + CLOSE_ANGLE_BRACKET) == 0) { 304 found = true; 305 break; 306 } 307 } 308 if (!found) { 309 log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() 310 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 311 } 312 } 313 314 /** 315 * Checks for unused param tags for type parameters. 316 * @param tags tags from the Javadoc comment for the type definition. 317 * @param typeParamNames names of type parameters 318 */ 319 private void checkUnusedTypeParamTags( 320 final List<JavadocTag> tags, 321 final List<String> typeParamNames) { 322 for (int i = tags.size() - 1; i >= 0; i--) { 323 final JavadocTag tag = tags.get(i); 324 if (tag.isParamTag()) { 325 326 final String typeParamName = extractTypeParamNameFromTag(tag); 327 328 if (!typeParamNames.contains(typeParamName)) { 329 log(tag.getLineNo(), tag.getColumnNo(), 330 MSG_UNUSED_TAG, 331 JavadocTagInfo.PARAM.getText(), 332 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 333 } 334 } 335 } 336 } 337 338 /** 339 * Extracts type parameter name from tag. 340 * @param tag javadoc tag to extract parameter name 341 * @return extracts type parameter name from tag 342 */ 343 private static String extractTypeParamNameFromTag(JavadocTag tag) { 344 final String typeParamName; 345 final Matcher matchInAngleBrackets = 346 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); 347 if (matchInAngleBrackets.find()) { 348 typeParamName = matchInAngleBrackets.group(1).trim(); 349 } 350 else { 351 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; 352 } 353 return typeParamName; 354 } 355}