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