001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 java.util.ArrayList; 023import java.util.Arrays; 024import java.util.List; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailNode; 028import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 030 031/** 032 * <p> 033 * Checks that one blank line before the block tag if it is present in Javadoc. 034 * </p> 035 * <ul> 036 * <li> 037 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations 038 * if the Javadoc being examined by this check violates the tight html rules defined at 039 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules"> 040 * Tight-HTML Rules</a>. 041 * Type is {@code boolean}. 042 * Default value is {@code false}. 043 * </li> 044 * </ul> 045 * <p> 046 * To configure the check: 047 * </p> 048 * <pre> 049 * <module name="RequireEmptyLineBeforeBlockTagGroup"/> 050 * </pre> 051 * <p> 052 * By default, the check will report a violation if there is no blank line before the block tag, 053 * like in the example below. 054 * </p> 055 * <pre> 056 * /** 057 * * testMethod's javadoc. 058 * * @return something (violation) 059 * */ 060 * public boolean testMethod() { 061 * return false; 062 * } 063 * </pre> 064 * <p> 065 * Valid javadoc should have a blank line separating the parameters, return, throw, or 066 * other tags like in the example below. 067 * </p> 068 * <pre> 069 * /** 070 * * testMethod's javadoc. 071 * * 072 * * @param firstParam 073 * * @return something 074 * */ 075 * public boolean testMethod(int firstParam) { 076 * return false; 077 * } 078 * </pre> 079 * <p> 080 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 081 * </p> 082 * <p> 083 * Violation Message Keys: 084 * </p> 085 * <ul> 086 * <li> 087 * {@code javadoc.missed.html.close} 088 * </li> 089 * <li> 090 * {@code javadoc.parse.rule.error} 091 * </li> 092 * <li> 093 * {@code javadoc.tag.line.before} 094 * </li> 095 * <li> 096 * {@code javadoc.wrong.singleton.html.tag} 097 * </li> 098 * </ul> 099 * 100 * @since 8.36 101 */ 102@StatelessCheck 103public class RequireEmptyLineBeforeBlockTagGroupCheck extends AbstractJavadocCheck { 104 105 /** 106 * The key in "messages.properties" for the message that describes a tag in javadoc 107 * requiring an empty line before it. 108 */ 109 public static final String MSG_JAVADOC_TAG_LINE_BEFORE = "javadoc.tag.line.before"; 110 111 /** 112 * Case when space separates the tag and the asterisk like in the below example. 113 * <pre> 114 * /** 115 * * @param noSpace there is no space here 116 * </pre> 117 */ 118 private static final List<Integer> ONLY_TAG_VARIATION_1 = Arrays.asList( 119 JavadocTokenTypes.WS, 120 JavadocTokenTypes.LEADING_ASTERISK, 121 JavadocTokenTypes.NEWLINE); 122 123 /** 124 * Case when no space separates the tag and the asterisk like in the below example. 125 * <pre> 126 * /** 127 * *@param noSpace there is no space here 128 * </pre> 129 */ 130 private static final List<Integer> ONLY_TAG_VARIATION_2 = Arrays.asList( 131 JavadocTokenTypes.LEADING_ASTERISK, 132 JavadocTokenTypes.NEWLINE); 133 134 /** 135 * Returns only javadoc tags so visitJavadocToken only receives javadoc tags. 136 * 137 * @return only javadoc tags. 138 */ 139 @Override 140 public int[] getDefaultJavadocTokens() { 141 return new int[] { 142 JavadocTokenTypes.JAVADOC_TAG, 143 }; 144 } 145 146 @Override 147 public int[] getRequiredJavadocTokens() { 148 return getAcceptableJavadocTokens(); 149 } 150 151 /** 152 * Logs when there is no empty line before the tag. 153 * 154 * @param tagNode the at tag node to check for an empty space before it. 155 */ 156 @Override 157 public void visitJavadocToken(DetailNode tagNode) { 158 // No need to filter token because overridden getDefaultJavadocTokens ensures that we only 159 // receive JAVADOC_TAG DetailNode. 160 if (!isAnotherTagBefore(tagNode) 161 && !isOnlyTagInWholeJavadoc(tagNode) 162 && hasInsufficientConsecutiveNewlines(tagNode)) { 163 log(tagNode.getLineNumber(), 164 MSG_JAVADOC_TAG_LINE_BEFORE, 165 tagNode.getChildren()[0].getText()); 166 } 167 } 168 169 /** 170 * Returns true when there is a javadoc tag before the provided tagNode. 171 * 172 * @param tagNode the javadoc tag node, to look for more tags before it. 173 * @return true when there is a javadoc tag before the provided tagNode. 174 */ 175 private static boolean isAnotherTagBefore(DetailNode tagNode) { 176 boolean found = false; 177 DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode); 178 while (currentNode != null) { 179 if (currentNode.getType() == JavadocTokenTypes.JAVADOC_TAG) { 180 found = true; 181 break; 182 } 183 currentNode = JavadocUtil.getPreviousSibling(currentNode); 184 } 185 return found; 186 } 187 188 /** 189 * Returns true when there are is only whitespace and asterisks before the provided tagNode. 190 * When javadoc has only a javadoc tag like {@literal @} in it, the JAVADOC_TAG in a JAVADOC 191 * detail node will always have 2 or 3 siblings before it. The parse tree looks like: 192 * <pre> 193 * JAVADOC[3x0] 194 * |--NEWLINE[3x0] : [\n] 195 * |--LEADING_ASTERISK[4x0] : [ *] 196 * |--WS[4x2] : [ ] 197 * |--JAVADOC_TAG[4x3] : [@param T The bar.\n ] 198 * </pre> 199 * Or it can also look like: 200 * <pre> 201 * JAVADOC[3x0] 202 * |--NEWLINE[3x0] : [\n] 203 * |--LEADING_ASTERISK[4x0] : [ *] 204 * |--JAVADOC_TAG[4x3] : [@param T The bar.\n ] 205 * </pre> 206 * We do not include the variation 207 * <pre> 208 * /**@param noSpace there is no space here 209 * </pre> 210 * which results in the tree 211 * <pre> 212 * JAVADOC[3x0] 213 * |--JAVADOC_TAG[4x3] : [@param noSpace there is no space here\n ] 214 * </pre> 215 * because this one is invalid. We must recommend placing a blank line to separate @param 216 * from the first javadoc asterisks. 217 * 218 * @param tagNode the at tag node to check if there is nothing before it. 219 * @return true if there no text before the tagNode. 220 */ 221 private static boolean isOnlyTagInWholeJavadoc(DetailNode tagNode) { 222 final List<Integer> previousNodeTypes = new ArrayList<>(); 223 DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode); 224 while (currentNode != null) { 225 previousNodeTypes.add(currentNode.getType()); 226 currentNode = JavadocUtil.getPreviousSibling(currentNode); 227 } 228 return ONLY_TAG_VARIATION_1.equals(previousNodeTypes) 229 || ONLY_TAG_VARIATION_2.equals(previousNodeTypes); 230 } 231 232 /** 233 * Returns true when there are not enough empty lines before the provided tagNode. 234 * 235 * <p>Iterates through the previous siblings of the tagNode looking for empty lines until 236 * there are no more siblings or it hits something other than asterisk, whitespace or newline. 237 * If it finds at least one empty line, return true. Return false otherwise.</p> 238 * 239 * @param tagNode the tagNode to check if there are sufficient empty lines before it. 240 * @return true if there are not enough empty lines before the tagNode. 241 */ 242 private static boolean hasInsufficientConsecutiveNewlines(DetailNode tagNode) { 243 int count = 0; 244 DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode); 245 while (count <= 1 246 && currentNode != null 247 && (currentNode.getType() == JavadocTokenTypes.NEWLINE 248 || currentNode.getType() == JavadocTokenTypes.WS 249 || currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK)) { 250 if (currentNode.getType() == JavadocTokenTypes.NEWLINE) { 251 count++; 252 } 253 currentNode = JavadocUtil.getPreviousSibling(currentNode); 254 } 255 256 return count <= 1; 257 } 258}