001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.Check; 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 Check { 048 049 /** 050 * A key is pointing to the warning message text in "messages.properties" 051 * file. 052 */ 053 public static final String 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 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 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 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 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 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 /** The scope to check for. */ 092 private Scope scope = Scope.PRIVATE; 093 /** The visibility scope where Javadoc comments shouldn't be checked. **/ 094 private Scope excludeScope; 095 /** Compiled regexp to match author tag content. **/ 096 private Pattern authorFormatPattern; 097 /** Compiled regexp to match version tag content. **/ 098 private Pattern versionFormatPattern; 099 /** Regexp to match author tag content. */ 100 private String authorFormat; 101 /** Regexp to match version tag content. */ 102 private String versionFormat; 103 /** 104 * Controls whether to ignore errors when a method has type parameters but 105 * does not have matching param tags in the javadoc. Defaults to false. 106 */ 107 private boolean allowMissingParamTags; 108 /** Controls whether to flag errors for unknown tags. Defaults to false. */ 109 private boolean allowUnknownTags; 110 111 /** 112 * Sets the scope to check. 113 * @param from string to set scope from 114 */ 115 public void setScope(String from) { 116 scope = Scope.getInstance(from); 117 } 118 119 /** 120 * Set the excludeScope. 121 * @param excludeScope a {@code String} value 122 */ 123 public void setExcludeScope(String excludeScope) { 124 this.excludeScope = Scope.getInstance(excludeScope); 125 } 126 127 /** 128 * Set the author tag pattern. 129 * @param format a {@code String} value 130 */ 131 public void setAuthorFormat(String format) { 132 authorFormat = format; 133 authorFormatPattern = CommonUtils.createPattern(format); 134 } 135 136 /** 137 * Set the version format pattern. 138 * @param format a {@code String} value 139 */ 140 public void setVersionFormat(String format) { 141 versionFormat = format; 142 versionFormatPattern = CommonUtils.createPattern(format); 143 } 144 145 /** 146 * Controls whether to allow a type which has type parameters to 147 * omit matching param tags in the javadoc. Defaults to false. 148 * 149 * @param flag a {@code Boolean} value 150 */ 151 public void setAllowMissingParamTags(boolean flag) { 152 allowMissingParamTags = flag; 153 } 154 155 /** 156 * Controls whether to flag errors for unknown tags. Defaults to false. 157 * @param flag a {@code Boolean} value 158 */ 159 public void setAllowUnknownTags(boolean flag) { 160 allowUnknownTags = flag; 161 } 162 163 @Override 164 public int[] getDefaultTokens() { 165 return getAcceptableTokens(); 166 } 167 168 @Override 169 public int[] getAcceptableTokens() { 170 return new int[] { 171 TokenTypes.INTERFACE_DEF, 172 TokenTypes.CLASS_DEF, 173 TokenTypes.ENUM_DEF, 174 TokenTypes.ANNOTATION_DEF, 175 }; 176 } 177 178 @Override 179 public int[] getRequiredTokens() { 180 return getAcceptableTokens(); 181 } 182 183 @Override 184 public void visitToken(DetailAST ast) { 185 if (shouldCheck(ast)) { 186 final FileContents contents = getFileContents(); 187 final int lineNo = ast.getLineNo(); 188 final TextBlock cmt = contents.getJavadocBefore(lineNo); 189 if (cmt == null) { 190 log(lineNo, JAVADOC_MISSING); 191 } 192 else { 193 final List<JavadocTag> tags = getJavadocTags(cmt); 194 if (ScopeUtils.isOuterMostType(ast)) { 195 // don't check author/version for inner classes 196 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 197 authorFormatPattern, authorFormat); 198 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 199 versionFormatPattern, versionFormat); 200 } 201 202 final List<String> typeParamNames = 203 CheckUtils.getTypeParameterNames(ast); 204 205 if (!allowMissingParamTags) { 206 //Check type parameters that should exist, do 207 for (final String typeParamName : typeParamNames) { 208 checkTypeParamTag( 209 lineNo, tags, typeParamName); 210 } 211 } 212 213 checkUnusedTypeParamTags(tags, typeParamNames); 214 } 215 } 216 } 217 218 /** 219 * Whether we should check this node. 220 * @param ast a given node. 221 * @return whether we should check a given node. 222 */ 223 private boolean shouldCheck(final DetailAST ast) { 224 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 225 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 226 final Scope customScope; 227 228 if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) { 229 customScope = Scope.PUBLIC; 230 } 231 else { 232 customScope = declaredScope; 233 } 234 final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); 235 236 return customScope.isIn(scope) 237 && (surroundingScope == null || surroundingScope.isIn(scope)) 238 && (excludeScope == null 239 || !customScope.isIn(excludeScope) 240 || surroundingScope != null 241 && !surroundingScope.isIn(excludeScope)); 242 } 243 244 /** 245 * Gets all standalone tags from a given javadoc. 246 * @param cmt the Javadoc comment to process. 247 * @return all standalone tags from the given javadoc. 248 */ 249 private List<JavadocTag> getJavadocTags(TextBlock cmt) { 250 final JavadocTags tags = JavadocUtils.getJavadocTags(cmt, 251 JavadocUtils.JavadocTagType.BLOCK); 252 if (!allowUnknownTags) { 253 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 254 log(tag.getLine(), tag.getCol(), UNKNOWN_TAG, 255 tag.getName()); 256 } 257 } 258 return tags.getValidTags(); 259 } 260 261 /** 262 * Verifies that a type definition has a required tag. 263 * @param lineNo the line number for the type definition. 264 * @param tags tags from the Javadoc comment for the type definition. 265 * @param tagName the required tag name. 266 * @param formatPattern regexp for the tag value. 267 * @param format pattern for the tag value. 268 */ 269 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 270 Pattern formatPattern, String format) { 271 if (formatPattern == null) { 272 return; 273 } 274 275 int tagCount = 0; 276 final String tagPrefix = "@"; 277 for (int i = tags.size() - 1; i >= 0; i--) { 278 final JavadocTag tag = tags.get(i); 279 if (tag.getTagName().equals(tagName)) { 280 tagCount++; 281 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 282 log(lineNo, TAG_FORMAT, tagPrefix + tagName, format); 283 } 284 } 285 } 286 if (tagCount == 0) { 287 log(lineNo, MISSING_TAG, tagPrefix + tagName); 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 } 308 } 309 if (!found) { 310 log(lineNo, MISSING_TAG, JavadocTagInfo.PARAM.getText() 311 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 312 } 313 } 314 315 /** 316 * Checks for unused param tags for type parameters. 317 * @param tags tags from the Javadoc comment for the type definition. 318 * @param typeParamNames names of type parameters 319 */ 320 private void checkUnusedTypeParamTags( 321 final List<JavadocTag> tags, 322 final List<String> typeParamNames) { 323 final Pattern pattern = Pattern.compile("\\s*<([^>]+)>.*"); 324 for (int i = tags.size() - 1; i >= 0; i--) { 325 final JavadocTag tag = tags.get(i); 326 if (tag.isParamTag()) { 327 328 final Matcher matcher = pattern.matcher(tag.getFirstArg()); 329 if (matcher.find()) { 330 final String typeParamName = matcher.group(1).trim(); 331 if (!typeParamNames.contains(typeParamName)) { 332 log(tag.getLineNo(), tag.getColumnNo(), 333 UNUSED_TAG, 334 JavadocTagInfo.PARAM.getText(), 335 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 336 } 337 } 338 } 339 } 340 } 341}