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