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 boolean result = false; 198 DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine); 199 if (previousSibling != null 200 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) { 201 if (previousSibling.getType() == JavadocTokenTypes.TEXT 202 && previousSibling.getText().trim().isEmpty()) { 203 previousSibling = JavadocUtils.getPreviousSibling(previousSibling); 204 } 205 result = previousSibling != null 206 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK; 207 } 208 return result; 209 } 210 211 /** 212 * Determines whether or not the line with paragraph tag is first line in javadoc. 213 * @param paragraphTag paragraph tag. 214 * @return true, if line with paragraph tag is first line in javadoc. 215 */ 216 private static boolean isFirstParagraph(DetailNode paragraphTag) { 217 boolean result = true; 218 DetailNode previousNode = JavadocUtils.getPreviousSibling(paragraphTag); 219 while (previousNode != null) { 220 if (previousNode.getType() == JavadocTokenTypes.TEXT 221 && !previousNode.getText().trim().isEmpty() 222 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 223 && previousNode.getType() != JavadocTokenTypes.NEWLINE 224 && previousNode.getType() != JavadocTokenTypes.TEXT) { 225 result = false; 226 break; 227 } 228 previousNode = JavadocUtils.getPreviousSibling(previousNode); 229 } 230 return result; 231 } 232 233 /** 234 * Finds and returns nearest empty line in javadoc. 235 * @param node DetailNode node. 236 * @return Some nearest empty line in javadoc. 237 */ 238 private static DetailNode getNearestEmptyLine(DetailNode node) { 239 DetailNode newLine = JavadocUtils.getPreviousSibling(node); 240 while (newLine != null) { 241 final DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine); 242 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) { 243 break; 244 } 245 newLine = previousSibling; 246 } 247 return newLine; 248 } 249 250 /** 251 * Tests if NEWLINE node is a last node in javadoc. 252 * @param newLine NEWLINE node. 253 * @return true, if NEWLINE node is a last node in javadoc. 254 */ 255 private static boolean isLastEmptyLine(DetailNode newLine) { 256 boolean result = true; 257 DetailNode nextNode = JavadocUtils.getNextSibling(newLine); 258 while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) { 259 if (nextNode.getType() == JavadocTokenTypes.TEXT 260 && !nextNode.getText().trim().isEmpty() 261 || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT) { 262 result = false; 263 break; 264 } 265 nextNode = JavadocUtils.getNextSibling(nextNode); 266 } 267 return result; 268 } 269 270 /** 271 * Tests whether the paragraph tag is immediately followed by the text. 272 * @param tag html tag. 273 * @return true, if the paragraph tag is immediately followed by the text. 274 */ 275 private static boolean isImmediatelyFollowedByText(DetailNode tag) { 276 final DetailNode nextSibling = JavadocUtils.getNextSibling(tag); 277 return nextSibling.getType() == JavadocTokenTypes.NEWLINE 278 || nextSibling.getType() == JavadocTokenTypes.EOF 279 || CommonUtils.startsWithChar(nextSibling.getText(), ' '); 280 } 281}