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