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 org.apache.commons.lang3.ArrayUtils; 027 028import com.puppycrawl.tools.checkstyle.api.Check; 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 Check { 050 051 /** 052 * A key is pointing to the warning message text in "messages.properties" 053 * file. 054 */ 055 public static final String 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 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 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 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 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 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 /** The scope to check for. */ 094 private Scope scope = Scope.PRIVATE; 095 /** The visibility scope where Javadoc comments shouldn't be checked. **/ 096 private Scope excludeScope; 097 /** Compiled regexp to match author tag content. **/ 098 private Pattern authorFormatPattern; 099 /** Compiled regexp to match version tag content. **/ 100 private Pattern versionFormatPattern; 101 /** Regexp to match author tag content. */ 102 private String authorFormat; 103 /** Regexp to match version tag content. */ 104 private String versionFormat; 105 /** 106 * Controls whether to ignore errors when a method has type parameters but 107 * does not have matching param tags in the javadoc. Defaults to false. 108 */ 109 private boolean allowMissingParamTags; 110 /** Controls whether to flag errors for unknown tags. Defaults to false. */ 111 private boolean allowUnknownTags; 112 113 /** 114 * Sets the scope to check. 115 * @param from string to set scope from 116 */ 117 public void setScope(String from) { 118 scope = Scope.getInstance(from); 119 } 120 121 /** 122 * Set the excludeScope. 123 * @param excludeScope a {@code String} value 124 */ 125 public void setExcludeScope(String excludeScope) { 126 this.excludeScope = Scope.getInstance(excludeScope); 127 } 128 129 /** 130 * Set the author tag pattern. 131 * @param format a {@code String} value 132 */ 133 public void setAuthorFormat(String format) { 134 authorFormat = format; 135 authorFormatPattern = CommonUtils.createPattern(format); 136 } 137 138 /** 139 * Set the version format pattern. 140 * @param format a {@code String} value 141 */ 142 public void setVersionFormat(String format) { 143 versionFormat = format; 144 versionFormatPattern = CommonUtils.createPattern(format); 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 ArrayUtils.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, 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 authorFormatPattern, authorFormat); 200 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 201 versionFormatPattern, 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 DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 227 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 228 final Scope customScope; 229 230 if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) { 231 customScope = Scope.PUBLIC; 232 } 233 else { 234 customScope = declaredScope; 235 } 236 final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); 237 238 return customScope.isIn(scope) 239 && (surroundingScope == null || surroundingScope.isIn(scope)) 240 && (excludeScope == null 241 || !customScope.isIn(excludeScope) 242 || surroundingScope != null 243 && !surroundingScope.isIn(excludeScope)); 244 } 245 246 /** 247 * Gets all standalone tags from a given javadoc. 248 * @param textBlock the Javadoc comment to process. 249 * @return all standalone tags from the given javadoc. 250 */ 251 private List<JavadocTag> getJavadocTags(TextBlock textBlock) { 252 final JavadocTags tags = JavadocUtils.getJavadocTags(textBlock, 253 JavadocUtils.JavadocTagType.BLOCK); 254 if (!allowUnknownTags) { 255 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 256 log(tag.getLine(), tag.getCol(), UNKNOWN_TAG, 257 tag.getName()); 258 } 259 } 260 return tags.getValidTags(); 261 } 262 263 /** 264 * Verifies that a type definition has a required tag. 265 * @param lineNo the line number for the type definition. 266 * @param tags tags from the Javadoc comment for the type definition. 267 * @param tagName the required tag name. 268 * @param formatPattern regexp for the tag value. 269 * @param format pattern for the tag value. 270 */ 271 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 272 Pattern formatPattern, String format) { 273 if (formatPattern == null) { 274 return; 275 } 276 277 int tagCount = 0; 278 final String tagPrefix = "@"; 279 for (int i = tags.size() - 1; i >= 0; i--) { 280 final JavadocTag tag = tags.get(i); 281 if (tag.getTagName().equals(tagName)) { 282 tagCount++; 283 if (!formatPattern.matcher(tag.getFirstArg()).find()) { 284 log(lineNo, TAG_FORMAT, tagPrefix + tagName, format); 285 } 286 } 287 } 288 if (tagCount == 0) { 289 log(lineNo, MISSING_TAG, tagPrefix + tagName); 290 } 291 } 292 293 /** 294 * Verifies that a type definition has the specified param tag for 295 * the specified type parameter name. 296 * @param lineNo the line number for the type definition. 297 * @param tags tags from the Javadoc comment for the type definition. 298 * @param typeParamName the name of the type parameter 299 */ 300 private void checkTypeParamTag(final int lineNo, 301 final List<JavadocTag> tags, final String typeParamName) { 302 boolean found = false; 303 for (int i = tags.size() - 1; i >= 0; i--) { 304 final JavadocTag tag = tags.get(i); 305 if (tag.isParamTag() 306 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET 307 + typeParamName + CLOSE_ANGLE_BRACKET) == 0) { 308 found = true; 309 } 310 } 311 if (!found) { 312 log(lineNo, MISSING_TAG, JavadocTagInfo.PARAM.getText() 313 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 314 } 315 } 316 317 /** 318 * Checks for unused param tags for type parameters. 319 * @param tags tags from the Javadoc comment for the type definition. 320 * @param typeParamNames names of type parameters 321 */ 322 private void checkUnusedTypeParamTags( 323 final List<JavadocTag> tags, 324 final List<String> typeParamNames) { 325 final Pattern pattern = Pattern.compile("\\s*<([^>]+)>.*"); 326 for (int i = tags.size() - 1; i >= 0; i--) { 327 final JavadocTag tag = tags.get(i); 328 if (tag.isParamTag()) { 329 330 final Matcher matcher = pattern.matcher(tag.getFirstArg()); 331 if (matcher.find()) { 332 final String typeParamName = matcher.group(1).trim(); 333 if (!typeParamNames.contains(typeParamName)) { 334 log(tag.getLineNo(), tag.getColumnNo(), 335 UNUSED_TAG, 336 JavadocTagInfo.PARAM.getText(), 337 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); 338 } 339 } 340 } 341 } 342 } 343}