Coverage Report - com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
JavadocParagraphCheck
100%
74/74
100%
74/74
3.917
 
 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 com.puppycrawl.tools.checkstyle.api.DetailNode;
 23  
 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
 24  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 25  
 import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
 26  
 
 27  
 /**
 28  
  * Checks that:
 29  
  * <ul>
 30  
  * <li>There is one blank line between each of two paragraphs
 31  
  * and one blank line before the at-clauses block if it is present.</li>
 32  
  * <li>Each paragraph but the first has &lt;p&gt; immediately
 33  
  * before the first word, with no space after.</li>
 34  
  * </ul>
 35  
  *
 36  
  * <p>The check can be specified by option allowNewlineParagraph,
 37  
  * which says whether the &lt;p&gt; tag should be placed immediately before
 38  
  * the first word.
 39  
  *
 40  
  * <p>Default configuration:
 41  
  * </p>
 42  
  * <pre>
 43  
  * &lt;module name=&quot;JavadocParagraph&quot;/&gt;
 44  
  * </pre>
 45  
  *
 46  
  * <p>To allow newlines and spaces immediately after the &lt;p&gt; tag:
 47  
  * <pre>
 48  
  * &lt;module name=&quot;JavadocParagraph&quot;&gt;
 49  
  *      &lt;property name=&quot;allowNewlineParagraph&quot;
 50  
  *                   value==&quot;false&quot;/&gt;
 51  
  * &lt;/module&quot;&gt;
 52  
  * </pre>
 53  
  *
 54  
  * <p>In case of allowNewlineParagraph set to false
 55  
  * the following example will not have any violations:
 56  
  * <pre>
 57  
  *   /**
 58  
  *    * &lt;p&gt;
 59  
  *    * Some Javadoc.
 60  
  *    *
 61  
  *    * &lt;p&gt;  Some Javadoc.
 62  
  *    *
 63  
  *    * &lt;p&gt;
 64  
  *    * &lt;pre&gt;
 65  
  *    * Some preformatted Javadoc.
 66  
  *    * &lt;/pre&gt;
 67  
  *    *
 68  
  *    *&#47;
 69  
  * </pre>
 70  
  * @author maxvetrenko
 71  
  * @author Vladislav Lisetskiy
 72  
  *
 73  
  */
 74  16
 public class JavadocParagraphCheck extends AbstractJavadocCheck {
 75  
 
 76  
     /**
 77  
      * A key is pointing to the warning message text in "messages.properties"
 78  
      * file.
 79  
      */
 80  
     public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after";
 81  
 
 82  
     /**
 83  
      * A key is pointing to the warning message text in "messages.properties"
 84  
      * file.
 85  
      */
 86  
     public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before";
 87  
 
 88  
     /**
 89  
      * A key is pointing to the warning message text in "messages.properties"
 90  
      * file.
 91  
      */
 92  
     public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph";
 93  
 
 94  
     /**
 95  
      * A key is pointing to the warning message text in "messages.properties"
 96  
      * file.
 97  
      */
 98  
     public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag";
 99  
 
 100  
     /**
 101  
      * Whether the &lt;p&gt; tag should be placed immediately before the first word.
 102  
      */
 103  16
     private boolean allowNewlineParagraph = true;
 104  
 
 105  
     /**
 106  
      * Sets allowNewlineParagraph.
 107  
      * @param value value to set.
 108  
      */
 109  
     public void setAllowNewlineParagraph(boolean value) {
 110  2
         allowNewlineParagraph = value;
 111  2
     }
 112  
 
 113  
     @Override
 114  
     public int[] getDefaultJavadocTokens() {
 115  39
         return new int[] {
 116  
             JavadocTokenTypes.NEWLINE,
 117  
             JavadocTokenTypes.HTML_ELEMENT,
 118  
         };
 119  
     }
 120  
 
 121  
     @Override
 122  
     public int[] getRequiredJavadocTokens() {
 123  19
         return getAcceptableJavadocTokens();
 124  
     }
 125  
 
 126  
     @Override
 127  
     public void visitJavadocToken(DetailNode ast) {
 128  332
         if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
 129  70
             checkEmptyLine(ast);
 130  
         }
 131  262
         else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
 132  84
                 && JavadocUtils.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) {
 133  66
             checkParagraphTag(ast);
 134  
         }
 135  332
     }
 136  
 
 137  
     /**
 138  
      * Determines whether or not the next line after empty line has paragraph tag in the beginning.
 139  
      * @param newline NEWLINE node.
 140  
      */
 141  
     private void checkEmptyLine(DetailNode newline) {
 142  70
         final DetailNode nearestToken = getNearestNode(newline);
 143  70
         if (!isLastEmptyLine(newline) && nearestToken.getType() == JavadocTokenTypes.TEXT
 144  40
                 && !CommonUtils.isBlank(nearestToken.getText())) {
 145  6
             log(newline.getLineNumber(), MSG_TAG_AFTER);
 146  
         }
 147  70
     }
 148  
 
 149  
     /**
 150  
      * Determines whether or not the line with paragraph tag has previous empty line.
 151  
      * @param tag html tag.
 152  
      */
 153  
     private void checkParagraphTag(DetailNode tag) {
 154  66
         final DetailNode newLine = getNearestEmptyLine(tag);
 155  66
         if (isFirstParagraph(tag)) {
 156  6
             log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH);
 157  
         }
 158  60
         else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
 159  30
             log(tag.getLineNumber(), MSG_LINE_BEFORE);
 160  
         }
 161  66
         if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) {
 162  17
             log(tag.getLineNumber(), MSG_MISPLACED_TAG);
 163  
         }
 164  66
     }
 165  
 
 166  
     /**
 167  
      * Returns nearest node.
 168  
      * @param node DetailNode node.
 169  
      * @return nearest node.
 170  
      */
 171  
     private static DetailNode getNearestNode(DetailNode node) {
 172  70
         DetailNode tag = JavadocUtils.getNextSibling(node);
 173  138
         while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK
 174  72
                 || tag.getType() == JavadocTokenTypes.NEWLINE) {
 175  68
             tag = JavadocUtils.getNextSibling(tag);
 176  
         }
 177  70
         return tag;
 178  
     }
 179  
 
 180  
     /**
 181  
      * Determines whether or not the line is empty line.
 182  
      * @param newLine NEWLINE node.
 183  
      * @return true, if line is empty line.
 184  
      */
 185  
     private static boolean isEmptyLine(DetailNode newLine) {
 186  336
         boolean result = false;
 187  336
         DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine);
 188  336
         if (previousSibling != null
 189  292
                 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
 190  236
             if (previousSibling.getType() == JavadocTokenTypes.TEXT
 191  132
                     && CommonUtils.isBlank(previousSibling.getText())) {
 192  12
                 previousSibling = JavadocUtils.getPreviousSibling(previousSibling);
 193  
             }
 194  236
             result = previousSibling != null
 195  234
                     && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
 196  
         }
 197  336
         return result;
 198  
     }
 199  
 
 200  
     /**
 201  
      * Determines whether or not the line with paragraph tag is first line in javadoc.
 202  
      * @param paragraphTag paragraph tag.
 203  
      * @return true, if line with paragraph tag is first line in javadoc.
 204  
      */
 205  
     private static boolean isFirstParagraph(DetailNode paragraphTag) {
 206  66
         boolean result = true;
 207  66
         DetailNode previousNode = JavadocUtils.getPreviousSibling(paragraphTag);
 208  254
         while (previousNode != null) {
 209  248
             if (previousNode.getType() == JavadocTokenTypes.TEXT
 210  102
                     && !CommonUtils.isBlank(previousNode.getText())
 211  190
                 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
 212  118
                     && previousNode.getType() != JavadocTokenTypes.NEWLINE
 213  46
                     && previousNode.getType() != JavadocTokenTypes.TEXT) {
 214  60
                 result = false;
 215  60
                 break;
 216  
             }
 217  188
             previousNode = JavadocUtils.getPreviousSibling(previousNode);
 218  
         }
 219  66
         return result;
 220  
     }
 221  
 
 222  
     /**
 223  
      * Finds and returns nearest empty line in javadoc.
 224  
      * @param node DetailNode node.
 225  
      * @return Some nearest empty line in javadoc.
 226  
      */
 227  
     private static DetailNode getNearestEmptyLine(DetailNode node) {
 228  66
         DetailNode newLine = JavadocUtils.getPreviousSibling(node);
 229  390
         while (newLine != null) {
 230  358
             final DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine);
 231  358
             if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
 232  34
                 break;
 233  
             }
 234  324
             newLine = previousSibling;
 235  324
         }
 236  66
         return newLine;
 237  
     }
 238  
 
 239  
     /**
 240  
      * Tests if NEWLINE node is a last node in javadoc.
 241  
      * @param newLine NEWLINE node.
 242  
      * @return true, if NEWLINE node is a last node in javadoc.
 243  
      */
 244  
     private static boolean isLastEmptyLine(DetailNode newLine) {
 245  70
         boolean result = true;
 246  70
         DetailNode nextNode = JavadocUtils.getNextSibling(newLine);
 247  206
         while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) {
 248  178
             if (nextNode.getType() == JavadocTokenTypes.TEXT
 249  46
                     && !CommonUtils.isBlank(nextNode.getText())
 250  172
                     || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT) {
 251  42
                 result = false;
 252  42
                 break;
 253  
             }
 254  136
             nextNode = JavadocUtils.getNextSibling(nextNode);
 255  
         }
 256  70
         return result;
 257  
     }
 258  
 
 259  
     /**
 260  
      * Tests whether the paragraph tag is immediately followed by the text.
 261  
      * @param tag html tag.
 262  
      * @return true, if the paragraph tag is immediately followed by the text.
 263  
      */
 264  
     private static boolean isImmediatelyFollowedByText(DetailNode tag) {
 265  44
         final DetailNode nextSibling = JavadocUtils.getNextSibling(tag);
 266  88
         return nextSibling.getType() == JavadocTokenTypes.NEWLINE
 267  43
                 || nextSibling.getType() == JavadocTokenTypes.EOF
 268  42
                 || CommonUtils.startsWithChar(nextSibling.getText(), ' ');
 269  
     }
 270  
 }