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