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 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.wrong.singleton.html.tag} 083 * </li> 084 * </ul> 085 * 086 * @since 6.0 087 */ 088@StatelessCheck 089public class JavadocParagraphCheck extends AbstractJavadocCheck { 090 091 /** 092 * A key is pointing to the warning message text in "messages.properties" 093 * file. 094 */ 095 public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after"; 096 097 /** 098 * A key is pointing to the warning message text in "messages.properties" 099 * file. 100 */ 101 public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before"; 102 103 /** 104 * A key is pointing to the warning message text in "messages.properties" 105 * file. 106 */ 107 public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph"; 108 109 /** 110 * A key is pointing to the warning message text in "messages.properties" 111 * file. 112 */ 113 public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag"; 114 115 /** 116 * Control whether the <p> tag should be placed immediately before the first word. 117 */ 118 private boolean allowNewlineParagraph = true; 119 120 /** 121 * Setter to control whether the <p> tag should be placed 122 * immediately before the first word. 123 * 124 * @param value value to set. 125 * @since 6.9 126 */ 127 public void setAllowNewlineParagraph(boolean value) { 128 allowNewlineParagraph = value; 129 } 130 131 @Override 132 public int[] getDefaultJavadocTokens() { 133 return new int[] { 134 JavadocTokenTypes.NEWLINE, 135 JavadocTokenTypes.HTML_ELEMENT, 136 }; 137 } 138 139 @Override 140 public int[] getRequiredJavadocTokens() { 141 return getAcceptableJavadocTokens(); 142 } 143 144 @Override 145 public void visitJavadocToken(DetailNode ast) { 146 if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) { 147 checkEmptyLine(ast); 148 } 149 else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT 150 && JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) { 151 checkParagraphTag(ast); 152 } 153 } 154 155 /** 156 * Determines whether or not the next line after empty line has paragraph tag in the beginning. 157 * 158 * @param newline NEWLINE node. 159 */ 160 private void checkEmptyLine(DetailNode newline) { 161 final DetailNode nearestToken = getNearestNode(newline); 162 if (nearestToken.getType() == JavadocTokenTypes.TEXT 163 && !CommonUtil.isBlank(nearestToken.getText())) { 164 log(newline.getLineNumber(), MSG_TAG_AFTER); 165 } 166 } 167 168 /** 169 * Determines whether or not the line with paragraph tag has previous empty line. 170 * 171 * @param tag html tag. 172 */ 173 private void checkParagraphTag(DetailNode tag) { 174 final DetailNode newLine = getNearestEmptyLine(tag); 175 if (isFirstParagraph(tag)) { 176 log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH); 177 } 178 else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) { 179 log(tag.getLineNumber(), MSG_LINE_BEFORE); 180 } 181 if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) { 182 log(tag.getLineNumber(), MSG_MISPLACED_TAG); 183 } 184 } 185 186 /** 187 * Returns nearest node. 188 * 189 * @param node DetailNode node. 190 * @return nearest node. 191 */ 192 private static DetailNode getNearestNode(DetailNode node) { 193 DetailNode currentNode = node; 194 while (currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK 195 || currentNode.getType() == JavadocTokenTypes.NEWLINE) { 196 currentNode = JavadocUtil.getNextSibling(currentNode); 197 } 198 return currentNode; 199 } 200 201 /** 202 * Determines whether or not the line is empty line. 203 * 204 * @param newLine NEWLINE node. 205 * @return true, if line is empty line. 206 */ 207 private static boolean isEmptyLine(DetailNode newLine) { 208 boolean result = false; 209 DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine); 210 if (previousSibling != null 211 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) { 212 if (previousSibling.getType() == JavadocTokenTypes.TEXT 213 && CommonUtil.isBlank(previousSibling.getText())) { 214 previousSibling = JavadocUtil.getPreviousSibling(previousSibling); 215 } 216 result = previousSibling != null 217 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK; 218 } 219 return result; 220 } 221 222 /** 223 * Determines whether or not the line with paragraph tag is first line in javadoc. 224 * 225 * @param paragraphTag paragraph tag. 226 * @return true, if line with paragraph tag is first line in javadoc. 227 */ 228 private static boolean isFirstParagraph(DetailNode paragraphTag) { 229 boolean result = true; 230 DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag); 231 while (previousNode != null) { 232 if (previousNode.getType() == JavadocTokenTypes.TEXT 233 && !CommonUtil.isBlank(previousNode.getText()) 234 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK 235 && previousNode.getType() != JavadocTokenTypes.NEWLINE 236 && previousNode.getType() != JavadocTokenTypes.TEXT) { 237 result = false; 238 break; 239 } 240 previousNode = JavadocUtil.getPreviousSibling(previousNode); 241 } 242 return result; 243 } 244 245 /** 246 * Finds and returns nearest empty line in javadoc. 247 * 248 * @param node DetailNode node. 249 * @return Some nearest empty line in javadoc. 250 */ 251 private static DetailNode getNearestEmptyLine(DetailNode node) { 252 DetailNode newLine = JavadocUtil.getPreviousSibling(node); 253 while (newLine != null) { 254 final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine); 255 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) { 256 break; 257 } 258 newLine = previousSibling; 259 } 260 return newLine; 261 } 262 263 /** 264 * Tests whether the paragraph tag is immediately followed by the text. 265 * 266 * @param tag html tag. 267 * @return true, if the paragraph tag is immediately followed by the text. 268 */ 269 private static boolean isImmediatelyFollowedByText(DetailNode tag) { 270 final DetailNode nextSibling = JavadocUtil.getNextSibling(tag); 271 return nextSibling.getType() == JavadocTokenTypes.NEWLINE 272 || nextSibling.getType() == JavadocTokenTypes.EOF 273 || nextSibling.getText().startsWith(" "); 274 } 275 276}