001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.javadoc; 020 021import com.puppycrawl.tools.checkstyle.api.Check; 022import com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.FileContents; 024import com.puppycrawl.tools.checkstyle.api.JavadocTagInfo; 025import com.puppycrawl.tools.checkstyle.api.Scope; 026import com.puppycrawl.tools.checkstyle.api.ScopeUtils; 027import com.puppycrawl.tools.checkstyle.api.TextBlock; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.api.Utils; 030import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 031import java.util.List; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034import java.util.regex.PatternSyntaxException; 035import org.apache.commons.beanutils.ConversionException; 036 037/** 038 * Checks the Javadoc of a type. 039 * 040 * @author Oliver Burn 041 * @author Michael Tamm 042 */ 043public class JavadocTypeCheck 044 extends Check 045{ 046 /** the scope to check for */ 047 private Scope scope = Scope.PRIVATE; 048 /** the visibility scope where Javadoc comments shouldn't be checked **/ 049 private Scope excludeScope; 050 /** compiled regexp to match author tag content **/ 051 private Pattern authorFormatPattern; 052 /** compiled regexp to match version tag content **/ 053 private Pattern versionFormatPattern; 054 /** regexp to match author tag content */ 055 private String authorFormat; 056 /** regexp to match version tag content */ 057 private String versionFormat; 058 /** 059 * controls whether to ignore errors when a method has type parameters but 060 * does not have matching param tags in the javadoc. Defaults to false. 061 */ 062 private boolean allowMissingParamTags; 063 /** controls whether to flag errors for unknown tags. Defaults to false. */ 064 private boolean allowUnknownTags; 065 066 /** 067 * Sets the scope to check. 068 * @param from string to set scope from 069 */ 070 public void setScope(String from) 071 { 072 scope = Scope.getInstance(from); 073 } 074 075 /** 076 * Set the excludeScope. 077 * @param scope a <code>String</code> value 078 */ 079 public void setExcludeScope(String scope) 080 { 081 excludeScope = Scope.getInstance(scope); 082 } 083 084 /** 085 * Set the author tag pattern. 086 * @param format a <code>String</code> value 087 * @throws ConversionException unable to parse aFormat 088 */ 089 public void setAuthorFormat(String format) 090 throws ConversionException 091 { 092 try { 093 authorFormat = format; 094 authorFormatPattern = Utils.getPattern(format); 095 } 096 catch (final PatternSyntaxException e) { 097 throw new ConversionException("unable to parse " + format, e); 098 } 099 } 100 101 /** 102 * Set the version format pattern. 103 * @param format a <code>String</code> value 104 * @throws ConversionException unable to parse aFormat 105 */ 106 public void setVersionFormat(String format) 107 throws ConversionException 108 { 109 try { 110 versionFormat = format; 111 versionFormatPattern = Utils.getPattern(format); 112 } 113 catch (final PatternSyntaxException e) { 114 throw new ConversionException("unable to parse " + format, e); 115 } 116 117 } 118 119 /** 120 * Controls whether to allow a type which has type parameters to 121 * omit matching param tags in the javadoc. Defaults to false. 122 * 123 * @param flag a <code>Boolean</code> value 124 */ 125 public void setAllowMissingParamTags(boolean flag) 126 { 127 allowMissingParamTags = flag; 128 } 129 130 /** 131 * Controls whether to flag errors for unknown tags. Defaults to false. 132 * @param flag a <code>Boolean</code> value 133 */ 134 public void setAllowUnknownTags(boolean flag) 135 { 136 allowUnknownTags = flag; 137 } 138 139 @Override 140 public int[] getDefaultTokens() 141 { 142 return new int[] { 143 TokenTypes.INTERFACE_DEF, 144 TokenTypes.CLASS_DEF, 145 TokenTypes.ENUM_DEF, 146 TokenTypes.ANNOTATION_DEF, 147 }; 148 } 149 150 @Override 151 public void visitToken(DetailAST ast) 152 { 153 if (shouldCheck(ast)) { 154 final FileContents contents = getFileContents(); 155 final int lineNo = ast.getLineNo(); 156 final TextBlock cmt = contents.getJavadocBefore(lineNo); 157 if (cmt == null) { 158 log(lineNo, "javadoc.missing"); 159 } 160 else if (ScopeUtils.isOuterMostType(ast)) { 161 // don't check author/version for inner classes 162 final List<JavadocTag> tags = getJavadocTags(cmt); 163 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 164 authorFormatPattern, authorFormat); 165 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 166 versionFormatPattern, versionFormat); 167 168 final List<String> typeParamNames = 169 CheckUtils.getTypeParameterNames(ast); 170 171 if (!allowMissingParamTags) { 172 //Check type parameters that should exist, do 173 for (final String string : typeParamNames) { 174 checkTypeParamTag( 175 lineNo, tags, string); 176 } 177 } 178 179 checkUnusedTypeParamTags(tags, typeParamNames); 180 } 181 } 182 } 183 184 /** 185 * Whether we should check this node. 186 * @param ast a given node. 187 * @return whether we should check a given node. 188 */ 189 private boolean shouldCheck(final DetailAST ast) 190 { 191 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 192 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 193 final Scope scope = 194 ScopeUtils.inInterfaceOrAnnotationBlock(ast) 195 ? Scope.PUBLIC : declaredScope; 196 final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); 197 198 return scope.isIn(this.scope) 199 && ((surroundingScope == null) || surroundingScope.isIn(this.scope)) 200 && ((excludeScope == null) 201 || !scope.isIn(excludeScope) 202 || ((surroundingScope != null) 203 && !surroundingScope.isIn(excludeScope))); 204 } 205 206 /** 207 * Gets all standalone tags from a given javadoc. 208 * @param cmt the Javadoc comment to process. 209 * @return all standalone tags from the given javadoc. 210 */ 211 private List<JavadocTag> getJavadocTags(TextBlock cmt) 212 { 213 final JavadocTags tags = JavadocUtils.getJavadocTags(cmt, 214 JavadocUtils.JavadocTagType.BLOCK); 215 if (!allowUnknownTags) { 216 for (final InvalidJavadocTag tag : tags.getInvalidTags()) { 217 log(tag.getLine(), tag.getCol(), "javadoc.unknownTag", 218 tag.getName()); 219 } 220 } 221 return tags.getValidTags(); 222 } 223 224 /** 225 * Verifies that a type definition has a required tag. 226 * @param lineNo the line number for the type definition. 227 * @param tags tags from the Javadoc comment for the type definition. 228 * @param tagName the required tag name. 229 * @param formatPattern regexp for the tag value. 230 * @param format pattern for the tag value. 231 */ 232 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, 233 Pattern formatPattern, String format) 234 { 235 if (formatPattern == null) { 236 return; 237 } 238 239 int tagCount = 0; 240 for (int i = tags.size() - 1; i >= 0; i--) { 241 final JavadocTag tag = tags.get(i); 242 if (tag.getTagName().equals(tagName)) { 243 tagCount++; 244 if (!formatPattern.matcher(tag.getArg1()).find()) { 245 log(lineNo, "type.tagFormat", "@" + tagName, format); 246 } 247 } 248 } 249 if (tagCount == 0) { 250 log(lineNo, "type.missingTag", "@" + tagName); 251 } 252 } 253 254 /** 255 * Verifies that a type definition has the specified param tag for 256 * the specified type parameter name. 257 * @param lineNo the line number for the type definition. 258 * @param tags tags from the Javadoc comment for the type definition. 259 * @param typeParamName the name of the type parameter 260 */ 261 private void checkTypeParamTag(final int lineNo, 262 final List<JavadocTag> tags, final String typeParamName) 263 { 264 boolean found = false; 265 for (int i = tags.size() - 1; i >= 0; i--) { 266 final JavadocTag tag = tags.get(i); 267 if (tag.isParamTag() 268 && (tag.getArg1() != null) 269 && (tag.getArg1().indexOf("<" + typeParamName + ">") == 0)) 270 { 271 found = true; 272 } 273 } 274 if (!found) { 275 log(lineNo, "type.missingTag", 276 JavadocTagInfo.PARAM.getText() + " <" + typeParamName + ">"); 277 } 278 } 279 280 /** 281 * Checks for unused param tags for type parameters. 282 * @param tags tags from the Javadoc comment for the type definition. 283 * @param typeParamNames names of type parameters 284 */ 285 private void checkUnusedTypeParamTags( 286 final List<JavadocTag> tags, 287 final List<String> typeParamNames) 288 { 289 final Pattern pattern = Utils.getPattern("\\s*<([^>]+)>.*"); 290 for (int i = tags.size() - 1; i >= 0; i--) { 291 final JavadocTag tag = tags.get(i); 292 if (tag.isParamTag()) { 293 294 if (tag.getArg1() != null) { 295 296 final Matcher matcher = pattern.matcher(tag.getArg1()); 297 String typeParamName = null; 298 299 if (matcher.matches()) { 300 typeParamName = matcher.group(1).trim(); 301 if (!typeParamNames.contains(typeParamName)) { 302 log(tag.getLineNo(), tag.getColumnNo(), 303 "javadoc.unusedTag", 304 JavadocTagInfo.PARAM.getText(), 305 "<" + typeParamName + ">"); 306 } 307 } 308 else { 309 log(tag.getLineNo(), tag.getColumnNo(), 310 "javadoc.unusedTagGeneral"); 311 } 312 } 313 else { 314 log(tag.getLineNo(), tag.getColumnNo(), 315 "javadoc.unusedTagGeneral"); 316 } 317 } 318 } 319 } 320}