Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
SummaryJavadocCheck
100%
75/75
100%
48/48
3.4
 
 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.Arrays;
 23  
 import java.util.Collections;
 24  
 import java.util.HashSet;
 25  
 import java.util.Set;
 26  
 import java.util.regex.Pattern;
 27  
 
 28  
 import com.google.common.base.CharMatcher;
 29  
 import com.puppycrawl.tools.checkstyle.api.DetailNode;
 30  
 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
 31  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 32  
 import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
 33  
 
 34  
 /**
 35  
  * <p>
 36  
  * Checks that <a href=
 37  
  * "http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html#firstsentence">
 38  
  * Javadoc summary sentence</a> does not contain phrases that are not recommended to use.
 39  
  * Check also violate javadoc that does not contain first sentence.
 40  
  * By default Check validate that first sentence is not empty:</p><br>
 41  
  * <pre>
 42  
  * &lt;module name=&quot;SummaryJavadocCheck&quot;/&gt;
 43  
  * </pre>
 44  
  *
 45  
  * <p>To ensure that summary do not contain phrase like "This method returns",
 46  
  *  use following config:
 47  
  *
 48  
  * <pre>
 49  
  * &lt;module name=&quot;SummaryJavadocCheck&quot;&gt;
 50  
  *     &lt;property name=&quot;forbiddenSummaryFragments&quot;
 51  
  *     value=&quot;^This method returns.*&quot;/&gt;
 52  
  * &lt;/module&gt;
 53  
  * </pre>
 54  
  * <p>
 55  
  * To specify period symbol at the end of first javadoc sentence - use following config:
 56  
  * </p>
 57  
  * <pre>
 58  
  * &lt;module name=&quot;SummaryJavadocCheck&quot;&gt;
 59  
  *     &lt;property name=&quot;period&quot;
 60  
  *     value=&quot;period&quot;/&gt;
 61  
  * &lt;/module&gt;
 62  
  * </pre>
 63  
  *
 64  
  *
 65  
  * @author max
 66  
  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
 67  
  */
 68  19
 public class SummaryJavadocCheck extends AbstractJavadocCheck {
 69  
 
 70  
     /**
 71  
      * A key is pointing to the warning message text in "messages.properties"
 72  
      * file.
 73  
      */
 74  
     public static final String MSG_SUMMARY_FIRST_SENTENCE = "summary.first.sentence";
 75  
 
 76  
     /**
 77  
      * A key is pointing to the warning message text in "messages.properties"
 78  
      * file.
 79  
      */
 80  
     public static final String MSG_SUMMARY_JAVADOC = "summary.javaDoc";
 81  
     /**
 82  
      * A key is pointing to the warning message text in "messages.properties"
 83  
      * file.
 84  
      */
 85  
     public static final String MSG_SUMMARY_JAVADOC_MISSING = "summary.javaDoc.missing";
 86  
     /**
 87  
      * This regexp is used to convert multiline javadoc to single line without stars.
 88  
      */
 89  2
     private static final Pattern JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN =
 90  2
             Pattern.compile("\n[ ]+(\\*)|^[ ]+(\\*)");
 91  
 
 92  
     /** Period literal. */
 93  
     private static final String PERIOD = ".";
 94  
 
 95  
     /** Set of allowed Tokens tags in summary java doc. */
 96  4
     private static final Set<Integer> ALLOWED_TYPES = Collections.unmodifiableSet(
 97  4
             new HashSet<>(Arrays.asList(JavadocTokenTypes.TEXT,
 98  2
                     JavadocTokenTypes.WS))
 99  
     );
 100  
 
 101  
     /** Regular expression for forbidden summary fragments. */
 102  19
     private Pattern forbiddenSummaryFragments = CommonUtils.createPattern("^$");
 103  
 
 104  
     /** Period symbol at the end of first javadoc sentence. */
 105  19
     private String period = PERIOD;
 106  
 
 107  
     /**
 108  
      * Sets custom value of regular expression for forbidden summary fragments.
 109  
      * @param pattern a pattern.
 110  
      */
 111  
     public void setForbiddenSummaryFragments(Pattern pattern) {
 112  4
         forbiddenSummaryFragments = pattern;
 113  4
     }
 114  
 
 115  
     /**
 116  
      * Sets value of period symbol at the end of first javadoc sentence.
 117  
      * @param period period's value.
 118  
      */
 119  
     public void setPeriod(String period) {
 120  4
         this.period = period;
 121  4
     }
 122  
 
 123  
     @Override
 124  
     public int[] getDefaultJavadocTokens() {
 125  51
         return new int[] {
 126  
             JavadocTokenTypes.JAVADOC,
 127  
         };
 128  
     }
 129  
 
 130  
     @Override
 131  
     public int[] getRequiredJavadocTokens() {
 132  25
         return getAcceptableJavadocTokens();
 133  
     }
 134  
 
 135  
     @Override
 136  
     public void visitJavadocToken(DetailNode ast) {
 137  106
         if (!startsWithInheritDoc(ast)) {
 138  98
             final String summaryDoc = getSummarySentence(ast);
 139  98
             if (summaryDoc.isEmpty()) {
 140  19
                 log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC_MISSING);
 141  
             }
 142  79
             else if (!period.isEmpty()) {
 143  76
                 final String firstSentence = getFirstSentence(ast);
 144  76
                 final int endOfSentence = firstSentence.lastIndexOf(period);
 145  76
                 if (!summaryDoc.contains(period)) {
 146  10
                     log(ast.getLineNumber(), MSG_SUMMARY_FIRST_SENTENCE);
 147  
                 }
 148  76
                 if (endOfSentence != -1
 149  68
                         && containsForbiddenFragment(firstSentence.substring(0, endOfSentence))) {
 150  2
                     log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC);
 151  
                 }
 152  
             }
 153  
         }
 154  106
     }
 155  
 
 156  
     /**
 157  
      * Checks if the node starts with an {&#64;inheritDoc}.
 158  
      * @param root The root node to examine.
 159  
      * @return {@code true} if the javadoc starts with an {&#64;inheritDoc}.
 160  
      */
 161  
     private static boolean startsWithInheritDoc(DetailNode root) {
 162  106
         boolean found = false;
 163  106
         final DetailNode[] children = root.getChildren();
 164  
 
 165  343
         for (int i = 0; !found && i < children.length - 1; i++) {
 166  333
             final DetailNode child = children[i];
 167  333
             if (child.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG
 168  10
                     && child.getChildren()[1].getType() == JavadocTokenTypes.INHERIT_DOC_LITERAL) {
 169  8
                 found = true;
 170  
             }
 171  325
             else if (child.getType() != JavadocTokenTypes.LEADING_ASTERISK
 172  229
                     && !CommonUtils.isBlank(child.getText())) {
 173  96
                 break;
 174  
             }
 175  
         }
 176  
 
 177  106
         return found;
 178  
     }
 179  
 
 180  
     /**
 181  
      * Checks if period is at the end of sentence.
 182  
      * @param ast Javadoc root node.
 183  
      * @return error string
 184  
      */
 185  
     private static String getSummarySentence(DetailNode ast) {
 186  98
         boolean flag = true;
 187  98
         final StringBuilder result = new StringBuilder(256);
 188  723
         for (DetailNode child : ast.getChildren()) {
 189  640
             if (ALLOWED_TYPES.contains(child.getType())) {
 190  211
                 result.append(child.getText());
 191  
             }
 192  429
             else if (child.getType() == JavadocTokenTypes.HTML_ELEMENT
 193  10
                     && CommonUtils.isBlank(result.toString().trim())) {
 194  14
                 result.append(getStringInsideTag(result.toString(),
 195  7
                         child.getChildren()[0].getChildren()[0]));
 196  
             }
 197  422
             else if (child.getType() == JavadocTokenTypes.JAVADOC_TAG) {
 198  15
                 flag = false;
 199  
             }
 200  640
             if (!flag) {
 201  15
                 break;
 202  
             }
 203  
         }
 204  98
         return result.toString().trim();
 205  
     }
 206  
 
 207  
     /**
 208  
      * Concatenates string within text of html tags.
 209  
      * @param result javadoc string
 210  
      * @param detailNode javadoc tag node
 211  
      * @return java doc tag content appended in result
 212  
      */
 213  
     private static String getStringInsideTag(String result, DetailNode detailNode) {
 214  7
         final StringBuilder contents = new StringBuilder(result);
 215  7
         DetailNode tempNode = detailNode;
 216  19
         while (tempNode != null) {
 217  12
             if (tempNode.getType() == JavadocTokenTypes.TEXT) {
 218  2
                 contents.append(tempNode.getText());
 219  
             }
 220  12
             tempNode = JavadocUtils.getNextSibling(tempNode);
 221  
         }
 222  7
         return contents.toString();
 223  
     }
 224  
 
 225  
     /**
 226  
      * Finds and returns first sentence.
 227  
      * @param ast Javadoc root node.
 228  
      * @return first sentence.
 229  
      */
 230  
     private static String getFirstSentence(DetailNode ast) {
 231  148
         final StringBuilder result = new StringBuilder(256);
 232  148
         final String periodSuffix = PERIOD + ' ';
 233  888
         for (DetailNode child : ast.getChildren()) {
 234  
             final String text;
 235  766
             if (child.getChildren().length == 0) {
 236  694
                 text = child.getText();
 237  
             }
 238  
             else {
 239  72
                 text = getFirstSentence(child);
 240  
             }
 241  
 
 242  766
             if (child.getType() != JavadocTokenTypes.JAVADOC_INLINE_TAG
 243  746
                 && text.contains(periodSuffix)) {
 244  26
                 result.append(text.substring(0, text.indexOf(periodSuffix) + 1));
 245  26
                 break;
 246  
             }
 247  
             else {
 248  740
                 result.append(text);
 249  
             }
 250  
         }
 251  148
         return result.toString();
 252  
     }
 253  
 
 254  
     /**
 255  
      * Tests if first sentence contains forbidden summary fragment.
 256  
      * @param firstSentence String with first sentence.
 257  
      * @return true, if first sentence contains forbidden summary fragment.
 258  
      */
 259  
     private boolean containsForbiddenFragment(String firstSentence) {
 260  68
         String javadocText = JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN
 261  68
                 .matcher(firstSentence).replaceAll(" ");
 262  68
         javadocText = CharMatcher.whitespace().trimAndCollapseFrom(javadocText, ' ');
 263  68
         return forbiddenSummaryFragments.matcher(javadocText).find();
 264  
     }
 265  
 }