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