001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.DetailNode; 024import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 025import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 026import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 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 * 072 */ 073@StatelessCheck 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 && JavadocUtil.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 (nearestToken.getType() == JavadocTokenTypes.TEXT 144 && !CommonUtil.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 = JavadocUtil.getNextSibling(node); 173 while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK 174 || tag.getType() == JavadocTokenTypes.NEWLINE) { 175 tag = JavadocUtil.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 = JavadocUtil.getPreviousSibling(newLine); 188 if (previousSibling != null 189 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) { 190 if (previousSibling.getType() == JavadocTokenTypes.TEXT 191 && CommonUtil.isBlank(previousSibling.getText())) { 192 previousSibling = JavadocUtil.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 = JavadocUtil.getPreviousSibling(paragraphTag); 208 while (previousNode != null) { 209 if (previousNode.getType() == JavadocTokenTypes.TEXT 210 && !CommonUtil.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 = JavadocUtil.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 = JavadocUtil.getPreviousSibling(node); 229 while (newLine != null) { 230 final DetailNode previousSibling = JavadocUtil.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 whether the paragraph tag is immediately followed by the text. 241 * @param tag html tag. 242 * @return true, if the paragraph tag is immediately followed by the text. 243 */ 244 private static boolean isImmediatelyFollowedByText(DetailNode tag) { 245 final DetailNode nextSibling = JavadocUtil.getNextSibling(tag); 246 return nextSibling.getType() == JavadocTokenTypes.NEWLINE 247 || nextSibling.getType() == JavadocTokenTypes.EOF 248 || CommonUtil.startsWithChar(nextSibling.getText(), ' '); 249 } 250 251}