001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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[] getAcceptableTokens() { 124 return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN}; 125 } 126 127 @Override 128 public int[] getRequiredTokens() { 129 return getAcceptableTokens(); 130 } 131 132 @Override 133 public void visitJavadocToken(DetailNode ast) { 134 if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) { 135 checkEmptyLine(ast); 136 } 137 else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT 138 && JavadocUtils.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_OPEN) { 139 checkParagraphTag(ast); 140 } 141 } 142 143 /** 144 * Determines whether or not the next line after empty line has paragraph tag in the beginning. 145 * @param newline NEWLINE node. 146 */ 147 private void checkEmptyLine(DetailNode newline) { 148 final DetailNode nearestToken = getNearestNode(newline); 149 if (!isLastEmptyLine(newline) && nearestToken.getChildren().length > 1) { 150 log(newline.getLineNumber(), MSG_TAG_AFTER); 151 } 152 } 153 154 /** 155 * Determines whether or not the line with paragraph tag has previous empty line. 156 * @param tag html tag. 157 */ 158 private void checkParagraphTag(DetailNode tag) { 159 final DetailNode newLine = getNearestEmptyLine(tag); 160 if (isFirstParagraph(tag)) { 161 log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH); 162 } 163 else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) { 164 log(tag.getLineNumber(), MSG_LINE_BEFORE); 165 } 166 if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) { 167 log(tag.getLineNumber(), MSG_MISPLACED_TAG); 168 } 169 } 170 171 /** 172 * Returns nearest node. 173 * @param node DetailNode node. 174 * @return nearest node. 175 */ 176 private static DetailNode getNearestNode(DetailNode node) { 177 DetailNode tag = JavadocUtils.getNextSibling(node); 178 while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK 179 || tag.getType() == JavadocTokenTypes.NEWLINE) { 180 tag = JavadocUtils.getNextSibling(tag); 181 } 182 return tag; 183 } 184 185 /** 186 * Determines whether or not the line is empty line. 187 * @param newLine NEWLINE node. 188 * @return true, if line is empty line. 189 */ 190 private static boolean isEmptyLine(DetailNode newLine) { 191 DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine); 192 if (previousSibling == null 193 || previousSibling.getParent().getType() != JavadocTokenTypes.JAVADOC) { 194 return false; 195 } 196 if (previousSibling.getType() == JavadocTokenTypes.TEXT 197 && previousSibling.getChildren().length == 1) { 198 previousSibling = JavadocUtils.getPreviousSibling(previousSibling); 199 } 200 return previousSibling != null 201 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK; 202 } 203 204 /** 205 * Determines whether or not the line with paragraph tag is first line in javadoc. 206 * @param paragraphTag paragraph tag. 207 * @return true, if line with paragraph tag is first line in javadoc. 208 */ 209 private static boolean isFirstParagraph(DetailNode paragraphTag) { 210 DetailNode previousNode = JavadocUtils.getPreviousSibling(paragraphTag); 211 while (previousNode != null) { 212 if (previousNode.getType() == JavadocTokenTypes.TEXT 213 && previousNode.getChildren().length > 1 214 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 215 && previousNode.getType() != JavadocTokenTypes.NEWLINE 216 && previousNode.getType() != JavadocTokenTypes.TEXT) { 217 return false; 218 } 219 previousNode = JavadocUtils.getPreviousSibling(previousNode); 220 } 221 return true; 222 } 223 224 /** 225 * Finds and returns nearest empty line in javadoc. 226 * @param node DetailNode node. 227 * @return Some nearest empty line in javadoc. 228 */ 229 private static DetailNode getNearestEmptyLine(DetailNode node) { 230 DetailNode newLine = JavadocUtils.getPreviousSibling(node); 231 while (newLine != null) { 232 final DetailNode previousSibling = JavadocUtils.getPreviousSibling(newLine); 233 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) { 234 break; 235 } 236 newLine = previousSibling; 237 } 238 return newLine; 239 } 240 241 /** 242 * Tests if NEWLINE node is a last node in javadoc. 243 * @param newLine NEWLINE node. 244 * @return true, if NEWLINE node is a last node in javadoc. 245 */ 246 private static boolean isLastEmptyLine(DetailNode newLine) { 247 DetailNode nextNode = JavadocUtils.getNextSibling(newLine); 248 while (nextNode != null && nextNode.getType() != JavadocTokenTypes.JAVADOC_TAG) { 249 if (nextNode.getType() == JavadocTokenTypes.TEXT 250 && nextNode.getChildren().length > 1 251 || nextNode.getType() == JavadocTokenTypes.HTML_ELEMENT) { 252 return false; 253 } 254 nextNode = JavadocUtils.getNextSibling(nextNode); 255 } 256 return true; 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}