Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
JavadocTypeCheck
100%
91/91
100%
56/56
2.688
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.javadoc;
 21  
 
 22  
 import java.util.List;
 23  
 import java.util.regex.Matcher;
 24  
 import java.util.regex.Pattern;
 25  
 
 26  
 import com.puppycrawl.tools.checkstyle.StatelessCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 28  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 29  
 import com.puppycrawl.tools.checkstyle.api.FileContents;
 30  
 import com.puppycrawl.tools.checkstyle.api.Scope;
 31  
 import com.puppycrawl.tools.checkstyle.api.TextBlock;
 32  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 33  
 import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
 34  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 35  
 import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
 36  
 import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
 37  
 
 38  
 /**
 39  
  * Checks the Javadoc of a type.
 40  
  *
 41  
  * <p>Does not perform checks for author and version tags for inner classes, as
 42  
  * they should be redundant because of outer class.
 43  
  *
 44  
  * @author Oliver Burn
 45  
  * @author Michael Tamm
 46  
  */
 47  
 @StatelessCheck
 48  39
 public class JavadocTypeCheck
 49  
     extends AbstractCheck {
 50  
 
 51  
     /**
 52  
      * A key is pointing to the warning message text in "messages.properties"
 53  
      * file.
 54  
      */
 55  
     public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
 56  
 
 57  
     /**
 58  
      * A key is pointing to the warning message text in "messages.properties"
 59  
      * file.
 60  
      */
 61  
     public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag";
 62  
 
 63  
     /**
 64  
      * A key is pointing to the warning message text in "messages.properties"
 65  
      * file.
 66  
      */
 67  
     public static final String MSG_TAG_FORMAT = "type.tagFormat";
 68  
 
 69  
     /**
 70  
      * A key is pointing to the warning message text in "messages.properties"
 71  
      * file.
 72  
      */
 73  
     public static final String MSG_MISSING_TAG = "type.missingTag";
 74  
 
 75  
     /**
 76  
      * A key is pointing to the warning message text in "messages.properties"
 77  
      * file.
 78  
      */
 79  
     public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
 80  
 
 81  
     /**
 82  
      * A key is pointing to the warning message text in "messages.properties"
 83  
      * file.
 84  
      */
 85  
     public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
 86  
 
 87  
     /** Open angle bracket literal. */
 88  
     private static final String OPEN_ANGLE_BRACKET = "<";
 89  
 
 90  
     /** Close angle bracket literal. */
 91  
     private static final String CLOSE_ANGLE_BRACKET = ">";
 92  
 
 93  
     /** Pattern to match type name within angle brackets in javadoc param tag. */
 94  1
     private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
 95  1
             Pattern.compile("\\s*<([^>]+)>.*");
 96  
 
 97  
     /** Pattern to split type name field in javadoc param tag. */
 98  2
     private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER =
 99  1
             Pattern.compile("\\s+");
 100  
 
 101  
     /** The scope to check for. */
 102  39
     private Scope scope = Scope.PRIVATE;
 103  
     /** The visibility scope where Javadoc comments shouldn't be checked. **/
 104  
     private Scope excludeScope;
 105  
     /** Compiled regexp to match author tag content. **/
 106  
     private Pattern authorFormat;
 107  
     /** Compiled regexp to match version tag content. **/
 108  
     private Pattern versionFormat;
 109  
     /**
 110  
      * Controls whether to ignore errors when a method has type parameters but
 111  
      * does not have matching param tags in the javadoc. Defaults to false.
 112  
      */
 113  
     private boolean allowMissingParamTags;
 114  
     /** Controls whether to flag errors for unknown tags. Defaults to false. */
 115  
     private boolean allowUnknownTags;
 116  
 
 117  
     /**
 118  
      * Sets the scope to check.
 119  
      * @param scope a scope.
 120  
      */
 121  
     public void setScope(Scope scope) {
 122  9
         this.scope = scope;
 123  9
     }
 124  
 
 125  
     /**
 126  
      * Set the excludeScope.
 127  
      * @param excludeScope a scope.
 128  
      */
 129  
     public void setExcludeScope(Scope excludeScope) {
 130  2
         this.excludeScope = excludeScope;
 131  2
     }
 132  
 
 133  
     /**
 134  
      * Set the author tag pattern.
 135  
      * @param pattern a pattern.
 136  
      */
 137  
     public void setAuthorFormat(Pattern pattern) {
 138  4
         authorFormat = pattern;
 139  4
     }
 140  
 
 141  
     /**
 142  
      * Set the version format pattern.
 143  
      * @param pattern a pattern.
 144  
      */
 145  
     public void setVersionFormat(Pattern pattern) {
 146  4
         versionFormat = pattern;
 147  4
     }
 148  
 
 149  
     /**
 150  
      * Controls whether to allow a type which has type parameters to
 151  
      * omit matching param tags in the javadoc. Defaults to false.
 152  
      *
 153  
      * @param flag a {@code Boolean} value
 154  
      */
 155  
     public void setAllowMissingParamTags(boolean flag) {
 156  1
         allowMissingParamTags = flag;
 157  1
     }
 158  
 
 159  
     /**
 160  
      * Controls whether to flag errors for unknown tags. Defaults to false.
 161  
      * @param flag a {@code Boolean} value
 162  
      */
 163  
     public void setAllowUnknownTags(boolean flag) {
 164  1
         allowUnknownTags = flag;
 165  1
     }
 166  
 
 167  
     @Override
 168  
     public int[] getDefaultTokens() {
 169  36
         return getAcceptableTokens();
 170  
     }
 171  
 
 172  
     @Override
 173  
     public int[] getAcceptableTokens() {
 174  43
         return new int[] {
 175  
             TokenTypes.INTERFACE_DEF,
 176  
             TokenTypes.CLASS_DEF,
 177  
             TokenTypes.ENUM_DEF,
 178  
             TokenTypes.ANNOTATION_DEF,
 179  
         };
 180  
     }
 181  
 
 182  
     @Override
 183  
     public int[] getRequiredTokens() {
 184  40
         return CommonUtils.EMPTY_INT_ARRAY;
 185  
     }
 186  
 
 187  
     @Override
 188  
     public void visitToken(DetailAST ast) {
 189  173
         if (shouldCheck(ast)) {
 190  134
             final FileContents contents = getFileContents();
 191  134
             final int lineNo = ast.getLineNo();
 192  134
             final TextBlock textBlock = contents.getJavadocBefore(lineNo);
 193  134
             if (textBlock == null) {
 194  59
                 log(lineNo, MSG_JAVADOC_MISSING);
 195  
             }
 196  
             else {
 197  75
                 final List<JavadocTag> tags = getJavadocTags(textBlock);
 198  75
                 if (ScopeUtils.isOuterMostType(ast)) {
 199  
                     // don't check author/version for inner classes
 200  68
                     checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(),
 201  
                             authorFormat);
 202  68
                     checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(),
 203  
                             versionFormat);
 204  
                 }
 205  
 
 206  75
                 final List<String> typeParamNames =
 207  75
                     CheckUtils.getTypeParameterNames(ast);
 208  
 
 209  75
                 if (!allowMissingParamTags) {
 210  
                     //Check type parameters that should exist, do
 211  72
                     for (final String typeParamName : typeParamNames) {
 212  5
                         checkTypeParamTag(
 213  
                             lineNo, tags, typeParamName);
 214  5
                     }
 215  
                 }
 216  
 
 217  75
                 checkUnusedTypeParamTags(tags, typeParamNames);
 218  
             }
 219  
         }
 220  173
     }
 221  
 
 222  
     /**
 223  
      * Whether we should check this node.
 224  
      * @param ast a given node.
 225  
      * @return whether we should check a given node.
 226  
      */
 227  
     private boolean shouldCheck(final DetailAST ast) {
 228  
         final Scope customScope;
 229  
 
 230  173
         if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
 231  3
             customScope = Scope.PUBLIC;
 232  
         }
 233  
         else {
 234  170
             final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
 235  170
             customScope = ScopeUtils.getScopeFromMods(mods);
 236  
         }
 237  173
         final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
 238  
 
 239  346
         return customScope.isIn(scope)
 240  62
             && (surroundingScope == null || surroundingScope.isIn(scope))
 241  
             && (excludeScope == null
 242  10
                 || !customScope.isIn(excludeScope)
 243  
                 || surroundingScope != null
 244  3
                 && !surroundingScope.isIn(excludeScope));
 245  
     }
 246  
 
 247  
     /**
 248  
      * Gets all standalone tags from a given javadoc.
 249  
      * @param textBlock the Javadoc comment to process.
 250  
      * @return all standalone tags from the given javadoc.
 251  
      */
 252  
     private List<JavadocTag> getJavadocTags(TextBlock textBlock) {
 253  75
         final JavadocTags tags = JavadocUtils.getJavadocTags(textBlock,
 254  
             JavadocUtils.JavadocTagType.BLOCK);
 255  75
         if (!allowUnknownTags) {
 256  74
             for (final InvalidJavadocTag tag : tags.getInvalidTags()) {
 257  2
                 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG,
 258  1
                     tag.getName());
 259  1
             }
 260  
         }
 261  75
         return tags.getValidTags();
 262  
     }
 263  
 
 264  
     /**
 265  
      * Verifies that a type definition has a required tag.
 266  
      * @param lineNo the line number for the type definition.
 267  
      * @param tags tags from the Javadoc comment for the type definition.
 268  
      * @param tagName the required tag name.
 269  
      * @param formatPattern regexp for the tag value.
 270  
      */
 271  
     private void checkTag(int lineNo, List<JavadocTag> tags, String tagName,
 272  
                           Pattern formatPattern) {
 273  136
         if (formatPattern != null) {
 274  54
             int tagCount = 0;
 275  54
             final String tagPrefix = "@";
 276  134
             for (int i = tags.size() - 1; i >= 0; i--) {
 277  80
                 final JavadocTag tag = tags.get(i);
 278  80
                 if (tag.getTagName().equals(tagName)) {
 279  40
                     tagCount++;
 280  40
                     if (!formatPattern.matcher(tag.getFirstArg()).find()) {
 281  15
                         log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern());
 282  
                     }
 283  
                 }
 284  
             }
 285  54
             if (tagCount == 0) {
 286  14
                 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName);
 287  
             }
 288  
         }
 289  136
     }
 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  5
         boolean found = false;
 301  20
         for (int i = tags.size() - 1; i >= 0; i--) {
 302  18
             final JavadocTag tag = tags.get(i);
 303  18
             if (tag.isParamTag()
 304  12
                 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET
 305  
                         + typeParamName + CLOSE_ANGLE_BRACKET) == 0) {
 306  3
                 found = true;
 307  3
                 break;
 308  
             }
 309  
         }
 310  5
         if (!found) {
 311  2
             log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText()
 312  
                 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
 313  
         }
 314  5
     }
 315  
 
 316  
     /**
 317  
      * Checks for unused param tags for type parameters.
 318  
      * @param tags tags from the Javadoc comment for the type definition.
 319  
      * @param typeParamNames names of type parameters
 320  
      */
 321  
     private void checkUnusedTypeParamTags(
 322  
         final List<JavadocTag> tags,
 323  
         final List<String> typeParamNames) {
 324  178
         for (int i = tags.size() - 1; i >= 0; i--) {
 325  103
             final JavadocTag tag = tags.get(i);
 326  103
             if (tag.isParamTag()) {
 327  
 
 328  14
                 final String typeParamName = extractTypeParamNameFromTag(tag);
 329  
 
 330  14
                 if (!typeParamNames.contains(typeParamName)) {
 331  16
                     log(tag.getLineNo(), tag.getColumnNo(),
 332  
                             MSG_UNUSED_TAG,
 333  8
                             JavadocTagInfo.PARAM.getText(),
 334  
                             OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
 335  
                 }
 336  
             }
 337  
         }
 338  75
     }
 339  
 
 340  
     /**
 341  
      * Extracts type parameter name from tag.
 342  
      * @param tag javadoc tag to extract parameter name
 343  
      * @return extracts type parameter name from tag
 344  
      */
 345  
     private static String extractTypeParamNameFromTag(JavadocTag tag) {
 346  
         final String typeParamName;
 347  14
         final Matcher matchInAngleBrackets =
 348  14
                 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg());
 349  14
         if (matchInAngleBrackets.find()) {
 350  11
             typeParamName = matchInAngleBrackets.group(1).trim();
 351  
         }
 352  
         else {
 353  3
             typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0];
 354  
         }
 355  14
         return typeParamName;
 356  
     }
 357  
 }