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.CommonUtils; 025import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; 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 * @author maxvetrenko 071 * @author Vladislav Lisetskiy 072 * 073 */ 074public class JavadocParagraphCheck extends AbstractJavadocCheck { 075 076 /** 077 * A key is pointing to the warning message text in "messages.properties" 078 * file. 079 */ 080 public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after"; 081 082 /** 083 * A key is pointing to the warning message text in "messages.properties" 084 * file. 085 */ 086 public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before"; 087 088 /** 089 * A key is pointing to the warning message text in "messages.properties" 090 * file. 091 */ 092 public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph"; 093 094 /** 095 * A key is pointing to the warning message text in "messages.properties" 096 * file. 097 */ 098 public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag"; 099 100 /** 101 * Whether the <p> tag should be placed immediately before the first word. 102 */ 103 private boolean allowNewlineParagraph = true; 104 105 /** 106 * Sets allowNewlineParagraph. 107 * @param value value to set. 108 */ 109 public void setAllowNewlineParagraph(boolean value) { 110 allowNewlineParagraph = value; 111 } 112 113 @Override 114 public int[] getDefaultJavadocTokens() { 115 return new int[] { 116 JavadocTokenTypes.NEWLINE, 117 JavadocTokenTypes.HTML_ELEMENT, 118 }; 119 } 120 121 @Override 122 public int[] getRequiredJavadocTokens() { 123 return getAcceptableJavadocTokens(); 124 } 125 126 @Override 127 public void visitJavadocToken(DetailNode ast) { 128 if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) { 129 checkEmptyLine(ast); 130 } 131 else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT 132 && JavadocUtils.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) { 133 checkParagraphTag(ast); 134 } 135 } 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 final DetailNode nearestToken = getNearestNode(newline); 143 if (!isLastEmptyLine(newline) && nearestToken.getType() == JavadocTokenTypes.TEXT 144 && !CommonUtils.isBlank(nearestToken.getText())) { 145 log(newline.getLineNumber(), MSG_TAG_AFTER); 146 } 147 } 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 final DetailNode newLine = getNearestEmptyLine(tag); 155 if (isFirstParagraph(tag)) { 156 log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH); 157 } 158 else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) { 159 log(tag.getLineNumber(), MSG_LINE_BEFORE); 160 } 161 if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) { 162 log(tag.getLineNumber(), MSG_MISPLACED_TAG); 163 } 164 } 165 166 /** 167 * Returns nearest node. 168 * @param node DetailNode node. 169 * @return nearest node. 170 */ 171 private static DetailNode getNearestNode(DetailNode node) { 172 DetailNode tag = JavadocUtils.getNextSibling(node); 173 while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK 174 || tag.getType() == JavadocTokenTypes.NEWLINE) { 175 tag = JavadocUtils.getNextSibling(tag); 176 } 177 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 boolean result = false; 187 DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine); 188 if (previousSibling != null 189 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) { 190 if (previousSibling.getType() == JavadocTokenTypes.TEXT 191 && CommonUtils.isBlank(previousSibling.getText())) { 192 previousSibling = JavadocUtils.getPreviousSibling(previousSibling); 193 } 194 result = previousSibling != null 195 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK; 196 } 197 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 boolean result = true; 207 DetailNode previousNode = JavadocUtils.getPreviousSibling(paragraphTag); 208 while (previousNode != null) { 209 if (previousNode.getType() == JavadocTokenTypes.TEXT 210 && !CommonUtils.isBlank(previousNode.getText()) 211 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 212 && previousNode.getType() != JavadocTokenTypes.NEWLINE 213 && previousNode.getType() != JavadocTokenTypes.TEXT) { 214 result = false; 215 break; 216 } 217 previousNode = JavadocUtils.getPreviousSibling(previousNode); 218 } 219 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 DetailNode newLine = JavadocUtils.getPreviousSibling(node); 229 while (newLine != null) { 230 final DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine); 231 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) { 232 break; 233 } 234 newLine = previousSibling; 235 } 236 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 boolean result = true; 246 DetailNode nextNode = JavadocUtils.getNextSibling(newLine); 247 while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) { 248 if (nextNode.getType() == JavadocTokenTypes.TEXT 249 && !CommonUtils.isBlank(nextNode.getText()) 250 || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT) { 251 result = false; 252 break; 253 } 254 nextNode = JavadocUtils.getNextSibling(nextNode); 255 } 256 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 final DetailNode nextSibling = JavadocUtils.getNextSibling(tag); 266 return nextSibling.getType() == JavadocTokenTypes.NEWLINE 267 || nextSibling.getType() == JavadocTokenTypes.EOF 268 || CommonUtils.startsWithChar(nextSibling.getText(), ' '); 269 } 270 271}