001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 the original author or authors. 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//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.javadoc; 021 022import com.puppycrawl.tools.checkstyle.api.DetailNode; 023import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 024import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 025import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 026 027/** 028 * Checks that: 029 * <ul> 030 * <li>There is one blank line between each of two paragraphs 031 * and one blank line before the at-clauses block if it is present.</li> 032 * <li>Each paragraph but the first has <p> immediately 033 * before the first word, with no space after.</li> 034 * </ul> 035 * 036 * <p>The check can be specified by option allowNewlineParagraph, 037 * which says whether the <p> tag should be placed immediately before 038 * the first word. 039 * 040 * <p>Default configuration: 041 * </p> 042 * <pre> 043 * <module name="JavadocParagraph"/> 044 * </pre> 045 * 046 * <p>To allow newlines and spaces immediately after the <p> tag: 047 * <pre> 048 * <module name="JavadocParagraph"> 049 * <property name="allowNewlineParagraph" 050 * value=="false"/> 051 * </module"> 052 * </pre> 053 * 054 * <p>In case of allowNewlineParagraph set to false 055 * the following example will not have any violations: 056 * <pre> 057 * /** 058 * * <p> 059 * * Some Javadoc. 060 * * 061 * * <p> Some Javadoc. 062 * * 063 * * <p> 064 * * <pre> 065 * * Some preformatted Javadoc. 066 * * </pre> 067 * * 068 * */ 069 * </pre> 070 * 071 */ 072public class JavadocParagraphCheck extends AbstractJavadocCheck { 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" 076 * file. 077 */ 078 public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after"; 079 080 /** 081 * A key is pointing to the warning message text in "messages.properties" 082 * file. 083 */ 084 public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before"; 085 086 /** 087 * A key is pointing to the warning message text in "messages.properties" 088 * file. 089 */ 090 public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph"; 091 092 /** 093 * A key is pointing to the warning message text in "messages.properties" 094 * file. 095 */ 096 public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag"; 097 098 /** 099 * Whether the <p> tag should be placed immediately before the first word. 100 */ 101 private boolean allowNewlineParagraph = true; 102 103 /** 104 * Sets allowNewlineParagraph. 105 * @param value value to set. 106 */ 107 public void setAllowNewlineParagraph(boolean value) { 108 allowNewlineParagraph = value; 109 } 110 111 @Override 112 public int[] getDefaultJavadocTokens() { 113 return new int[] { 114 JavadocTokenTypes.NEWLINE, 115 JavadocTokenTypes.HTML_ELEMENT, 116 }; 117 } 118 119 @Override 120 public int[] getRequiredJavadocTokens() { 121 return getAcceptableJavadocTokens(); 122 } 123 124 @Override 125 public void visitJavadocToken(DetailNode ast) { 126 if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) { 127 checkEmptyLine(ast); 128 } 129 else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT 130 && JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) { 131 checkParagraphTag(ast); 132 } 133 } 134 135 /** 136 * Determines whether or not the next line after empty line has paragraph tag in the beginning. 137 * @param newline NEWLINE node. 138 */ 139 private void checkEmptyLine(DetailNode newline) { 140 final DetailNode nearestToken = getNearestNode(newline); 141 if (!isLastEmptyLine(newline) && nearestToken.getType() == JavadocTokenTypes.TEXT 142 && !CommonUtil.isBlank(nearestToken.getText())) { 143 log(newline.getLineNumber(), MSG_TAG_AFTER); 144 } 145 } 146 147 /** 148 * Determines whether or not the line with paragraph tag has previous empty line. 149 * @param tag html tag. 150 */ 151 private void checkParagraphTag(DetailNode tag) { 152 final DetailNode newLine = getNearestEmptyLine(tag); 153 if (isFirstParagraph(tag)) { 154 log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH); 155 } 156 else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) { 157 log(tag.getLineNumber(), MSG_LINE_BEFORE); 158 } 159 if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) { 160 log(tag.getLineNumber(), MSG_MISPLACED_TAG); 161 } 162 } 163 164 /** 165 * Returns nearest node. 166 * @param node DetailNode node. 167 * @return nearest node. 168 */ 169 private static DetailNode getNearestNode(DetailNode node) { 170 DetailNode tag = JavadocUtil.getNextSibling(node); 171 while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK 172 || tag.getType() == JavadocTokenTypes.NEWLINE) { 173 tag = JavadocUtil.getNextSibling(tag); 174 } 175 return tag; 176 } 177 178 /** 179 * Determines whether or not the line is empty line. 180 * @param newLine NEWLINE node. 181 * @return true, if line is empty line. 182 */ 183 private static boolean isEmptyLine(DetailNode newLine) { 184 boolean result = false; 185 DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine); 186 if (previousSibling != null 187 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) { 188 if (previousSibling.getType() == JavadocTokenTypes.TEXT 189 && CommonUtil.isBlank(previousSibling.getText())) { 190 previousSibling = JavadocUtil.getPreviousSibling(previousSibling); 191 } 192 result = previousSibling != null 193 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK; 194 } 195 return result; 196 } 197 198 /** 199 * Determines whether or not the line with paragraph tag is first line in javadoc. 200 * @param paragraphTag paragraph tag. 201 * @return true, if line with paragraph tag is first line in javadoc. 202 */ 203 private static boolean isFirstParagraph(DetailNode paragraphTag) { 204 boolean result = true; 205 DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag); 206 while (previousNode != null) { 207 if (previousNode.getType() == JavadocTokenTypes.TEXT 208 && !CommonUtil.isBlank(previousNode.getText()) 209 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 210 && previousNode.getType() != JavadocTokenTypes.NEWLINE 211 && previousNode.getType() != JavadocTokenTypes.TEXT) { 212 result = false; 213 break; 214 } 215 previousNode = JavadocUtil.getPreviousSibling(previousNode); 216 } 217 return result; 218 } 219 220 /** 221 * Finds and returns nearest empty line in javadoc. 222 * @param node DetailNode node. 223 * @return Some nearest empty line in javadoc. 224 */ 225 private static DetailNode getNearestEmptyLine(DetailNode node) { 226 DetailNode newLine = JavadocUtil.getPreviousSibling(node); 227 while (newLine != null) { 228 final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine); 229 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) { 230 break; 231 } 232 newLine = previousSibling; 233 } 234 return newLine; 235 } 236 237 /** 238 * Tests if NEWLINE node is a last node in javadoc. 239 * @param newLine NEWLINE node. 240 * @return true, if NEWLINE node is a last node in javadoc. 241 */ 242 private static boolean isLastEmptyLine(DetailNode newLine) { 243 boolean result = true; 244 DetailNode nextNode = JavadocUtil.getNextSibling(newLine); 245 while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) { 246 if (nextNode.getType() == JavadocTokenTypes.TEXT 247 && !CommonUtil.isBlank(nextNode.getText()) 248 || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT) { 249 result = false; 250 break; 251 } 252 nextNode = JavadocUtil.getNextSibling(nextNode); 253 } 254 return result; 255 } 256 257 /** 258 * Tests whether the paragraph tag is immediately followed by the text. 259 * @param tag html tag. 260 * @return true, if the paragraph tag is immediately followed by the text. 261 */ 262 private static boolean isImmediatelyFollowedByText(DetailNode tag) { 263 final DetailNode nextSibling = JavadocUtil.getNextSibling(tag); 264 return nextSibling.getType() == JavadocTokenTypes.NEWLINE 265 || nextSibling.getType() == JavadocTokenTypes.EOF 266 || CommonUtil.startsWithChar(nextSibling.getText(), ' '); 267 } 268 269}