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 mScope = Scope.PRIVATE; 048 /** the visibility scope where Javadoc comments shouldn't be checked **/ 049 private Scope mExcludeScope; 050 /** compiled regexp to match author tag content **/ 051 private Pattern mAuthorFormatPattern; 052 /** compiled regexp to match version tag content **/ 053 private Pattern mVersionFormatPattern; 054 /** regexp to match author tag content */ 055 private String mAuthorFormat; 056 /** regexp to match version tag content */ 057 private String mVersionFormat; 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 mAllowMissingParamTags; 063 /** controls whether to flag errors for unknown tags. Defaults to false. */ 064 private boolean mAllowUnknownTags; 065 066 /** 067 * Sets the scope to check. 068 * @param aFrom string to set scope from 069 */ 070 public void setScope(String aFrom) 071 { 072 mScope = Scope.getInstance(aFrom); 073 } 074 075 /** 076 * Set the excludeScope. 077 * @param aScope a <code>String</code> value 078 */ 079 public void setExcludeScope(String aScope) 080 { 081 mExcludeScope = Scope.getInstance(aScope); 082 } 083 084 /** 085 * Set the author tag pattern. 086 * @param aFormat a <code>String</code> value 087 * @throws ConversionException unable to parse aFormat 088 */ 089 public void setAuthorFormat(String aFormat) 090 throws ConversionException 091 { 092 try { 093 mAuthorFormat = aFormat; 094 mAuthorFormatPattern = Utils.getPattern(aFormat); 095 } 096 catch (final PatternSyntaxException e) { 097 throw new ConversionException("unable to parse " + aFormat, e); 098 } 099 } 100 101 /** 102 * Set the version format pattern. 103 * @param aFormat a <code>String</code> value 104 * @throws ConversionException unable to parse aFormat 105 */ 106 public void setVersionFormat(String aFormat) 107 throws ConversionException 108 { 109 try { 110 mVersionFormat = aFormat; 111 mVersionFormatPattern = Utils.getPattern(aFormat); 112 } 113 catch (final PatternSyntaxException e) { 114 throw new ConversionException("unable to parse " + aFormat, 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 aFlag a <code>Boolean</code> value 124 */ 125 public void setAllowMissingParamTags(boolean aFlag) 126 { 127 mAllowMissingParamTags = aFlag; 128 } 129 130 /** 131 * Controls whether to flag errors for unknown tags. Defaults to false. 132 * @param aFlag a <code>Boolean</code> value 133 */ 134 public void setAllowUnknownTags(boolean aFlag) 135 { 136 mAllowUnknownTags = aFlag; 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 aAST) 152 { 153 if (shouldCheck(aAST)) { 154 final FileContents contents = getFileContents(); 155 final int lineNo = aAST.getLineNo(); 156 final TextBlock cmt = contents.getJavadocBefore(lineNo); 157 if (cmt == null) { 158 log(lineNo, "javadoc.missing"); 159 } 160 else if (ScopeUtils.isOuterMostType(aAST)) { 161 // don't check author/version for inner classes 162 final List<JavadocTag> tags = getJavadocTags(cmt); 163 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), 164 mAuthorFormatPattern, mAuthorFormat); 165 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), 166 mVersionFormatPattern, mVersionFormat); 167 168 final List<String> typeParamNames = 169 CheckUtils.getTypeParameterNames(aAST); 170 171 if (!mAllowMissingParamTags) { 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 aAST a given node. 187 * @return whether we should check a given node. 188 */ 189 private boolean shouldCheck(final DetailAST aAST) 190 { 191 final DetailAST mods = aAST.findFirstToken(TokenTypes.MODIFIERS); 192 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 193 final Scope scope = 194 ScopeUtils.inInterfaceOrAnnotationBlock(aAST) 195 ? Scope.PUBLIC : declaredScope; 196 final Scope surroundingScope = ScopeUtils.getSurroundingScope(aAST); 197 198 return scope.isIn(mScope) 199 && ((surroundingScope == null) || surroundingScope.isIn(mScope)) 200 && ((mExcludeScope == null) 201 || !scope.isIn(mExcludeScope) 202 || ((surroundingScope != null) 203 && !surroundingScope.isIn(mExcludeScope))); 204 } 205 206 /** 207 * Gets all standalone tags from a given javadoc. 208 * @param aCmt the Javadoc comment to process. 209 * @return all standalone tags from the given javadoc. 210 */ 211 private List<JavadocTag> getJavadocTags(TextBlock aCmt) 212 { 213 final JavadocTags tags = JavadocUtils.getJavadocTags(aCmt, 214 JavadocUtils.JavadocTagType.BLOCK); 215 if (!mAllowUnknownTags) { 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 aLineNo the line number for the type definition. 227 * @param aTags tags from the Javadoc comment for the type definition. 228 * @param aTag the required tag name. 229 * @param aFormatPattern regexp for the tag value. 230 * @param aFormat pattern for the tag value. 231 */ 232 private void checkTag(int aLineNo, List<JavadocTag> aTags, String aTag, 233 Pattern aFormatPattern, String aFormat) 234 { 235 if (aFormatPattern == null) { 236 return; 237 } 238 239 int tagCount = 0; 240 for (int i = aTags.size() - 1; i >= 0; i--) { 241 final JavadocTag tag = aTags.get(i); 242 if (tag.getTagName().equals(aTag)) { 243 tagCount++; 244 if (!aFormatPattern.matcher(tag.getArg1()).find()) { 245 log(aLineNo, "type.tagFormat", "@" + aTag, aFormat); 246 } 247 } 248 } 249 if (tagCount == 0) { 250 log(aLineNo, "type.missingTag", "@" + aTag); 251 } 252 } 253 254 /** 255 * Verifies that a type definition has the specified param tag for 256 * the specified type parameter name. 257 * @param aLineNo the line number for the type definition. 258 * @param aTags tags from the Javadoc comment for the type definition. 259 * @param aTypeParamName the name of the type parameter 260 */ 261 private void checkTypeParamTag(final int aLineNo, 262 final List<JavadocTag> aTags, final String aTypeParamName) 263 { 264 boolean found = false; 265 for (int i = aTags.size() - 1; i >= 0; i--) { 266 final JavadocTag tag = aTags.get(i); 267 if (tag.isParamTag() 268 && (tag.getArg1() != null) 269 && (tag.getArg1().indexOf("<" + aTypeParamName + ">") == 0)) 270 { 271 found = true; 272 } 273 } 274 if (!found) { 275 log(aLineNo, "type.missingTag", 276 JavadocTagInfo.PARAM.getText() + " <" + aTypeParamName + ">"); 277 } 278 } 279 280 /** 281 * Checks for unused param tags for type parameters. 282 * @param aTags tags from the Javadoc comment for the type definition. 283 * @param aTypeParamNames names of type parameters 284 */ 285 private void checkUnusedTypeParamTags( 286 final List<JavadocTag> aTags, 287 final List<String> aTypeParamNames) 288 { 289 final Pattern pattern = Utils.getPattern("\\s*<([^>]+)>.*"); 290 for (int i = aTags.size() - 1; i >= 0; i--) { 291 final JavadocTag tag = aTags.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 (!aTypeParamNames.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}