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 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 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 047 * </p> 048 * <p> 049 * Violation Message Keys: 050 * </p> 051 * <ul> 052 * <li> 053 * {@code javadoc.missed.html.close} 054 * </li> 055 * <li> 056 * {@code javadoc.parse.rule.error} 057 * </li> 058 * <li> 059 * {@code javadoc.tag.line.before} 060 * </li> 061 * <li> 062 * {@code javadoc.unclosedHtml} 063 * </li> 064 * <li> 065 * {@code javadoc.wrong.singleton.html.tag} 066 * </li> 067 * </ul> 068 * 069 * @since 8.36 070 */ 071@StatelessCheck 072public class RequireEmptyLineBeforeBlockTagGroupCheck extends AbstractJavadocCheck { 073 074 /** 075 * The key in "messages.properties" for the message that describes a tag in javadoc 076 * requiring an empty line before it. 077 */ 078 public static final String MSG_JAVADOC_TAG_LINE_BEFORE = "javadoc.tag.line.before"; 079 080 /** 081 * Case when space separates the tag and the asterisk like in the below example. 082 * <pre> 083 * /** 084 * * @param noSpace there is no space here 085 * </pre> 086 */ 087 private static final List<Integer> ONLY_TAG_VARIATION_1 = Arrays.asList( 088 JavadocTokenTypes.WS, 089 JavadocTokenTypes.LEADING_ASTERISK, 090 JavadocTokenTypes.NEWLINE); 091 092 /** 093 * Case when no space separates the tag and the asterisk like in the below example. 094 * <pre> 095 * /** 096 * *@param noSpace there is no space here 097 * </pre> 098 */ 099 private static final List<Integer> ONLY_TAG_VARIATION_2 = Arrays.asList( 100 JavadocTokenTypes.LEADING_ASTERISK, 101 JavadocTokenTypes.NEWLINE); 102 103 /** 104 * Returns only javadoc tags so visitJavadocToken only receives javadoc tags. 105 * 106 * @return only javadoc tags. 107 */ 108 @Override 109 public int[] getDefaultJavadocTokens() { 110 return new int[] { 111 JavadocTokenTypes.JAVADOC_TAG, 112 }; 113 } 114 115 @Override 116 public int[] getRequiredJavadocTokens() { 117 return getAcceptableJavadocTokens(); 118 } 119 120 /** 121 * Logs when there is no empty line before the tag. 122 * 123 * @param tagNode the at tag node to check for an empty space before it. 124 */ 125 @Override 126 public void visitJavadocToken(DetailNode tagNode) { 127 // No need to filter token because overridden getDefaultJavadocTokens ensures that we only 128 // receive JAVADOC_TAG DetailNode. 129 if (!isAnotherTagBefore(tagNode) 130 && !isOnlyTagInWholeJavadoc(tagNode) 131 && hasInsufficientConsecutiveNewlines(tagNode)) { 132 log(tagNode.getLineNumber(), 133 MSG_JAVADOC_TAG_LINE_BEFORE, 134 tagNode.getChildren()[0].getText()); 135 } 136 } 137 138 /** 139 * Returns true when there is a javadoc tag before the provided tagNode. 140 * 141 * @param tagNode the javadoc tag node, to look for more tags before it. 142 * @return true when there is a javadoc tag before the provided tagNode. 143 */ 144 private static boolean isAnotherTagBefore(DetailNode tagNode) { 145 boolean found = false; 146 DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode); 147 while (currentNode != null) { 148 if (currentNode.getType() == JavadocTokenTypes.JAVADOC_TAG) { 149 found = true; 150 break; 151 } 152 currentNode = JavadocUtil.getPreviousSibling(currentNode); 153 } 154 return found; 155 } 156 157 /** 158 * Returns true when there are is only whitespace and asterisks before the provided tagNode. 159 * When javadoc has only a javadoc tag like {@literal @} in it, the JAVADOC_TAG in a JAVADOC 160 * detail node will always have 2 or 3 siblings before it. The parse tree looks like: 161 * <pre> 162 * JAVADOC[3x0] 163 * |--NEWLINE[3x0] : [\n] 164 * |--LEADING_ASTERISK[4x0] : [ *] 165 * |--WS[4x2] : [ ] 166 * |--JAVADOC_TAG[4x3] : [@param T The bar.\n ] 167 * </pre> 168 * Or it can also look like: 169 * <pre> 170 * JAVADOC[3x0] 171 * |--NEWLINE[3x0] : [\n] 172 * |--LEADING_ASTERISK[4x0] : [ *] 173 * |--JAVADOC_TAG[4x3] : [@param T The bar.\n ] 174 * </pre> 175 * We do not include the variation 176 * <pre> 177 * /**@param noSpace there is no space here 178 * </pre> 179 * which results in the tree 180 * <pre> 181 * JAVADOC[3x0] 182 * |--JAVADOC_TAG[4x3] : [@param noSpace there is no space here\n ] 183 * </pre> 184 * because this one is invalid. We must recommend placing a blank line to separate @param 185 * from the first javadoc asterisks. 186 * 187 * @param tagNode the at tag node to check if there is nothing before it 188 * @return true if there is no text before the tagNode 189 */ 190 private static boolean isOnlyTagInWholeJavadoc(DetailNode tagNode) { 191 final List<Integer> previousNodeTypes = new ArrayList<>(); 192 DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode); 193 while (currentNode != null) { 194 previousNodeTypes.add(currentNode.getType()); 195 currentNode = JavadocUtil.getPreviousSibling(currentNode); 196 } 197 return ONLY_TAG_VARIATION_1.equals(previousNodeTypes) 198 || ONLY_TAG_VARIATION_2.equals(previousNodeTypes); 199 } 200 201 /** 202 * Returns true when there are not enough empty lines before the provided tagNode. 203 * 204 * <p>Iterates through the previous siblings of the tagNode looking for empty lines until 205 * there are no more siblings or it hits something other than asterisk, whitespace or newline. 206 * If it finds at least one empty line, return true. Return false otherwise.</p> 207 * 208 * @param tagNode the tagNode to check if there are sufficient empty lines before it. 209 * @return true if there are not enough empty lines before the tagNode. 210 */ 211 private static boolean hasInsufficientConsecutiveNewlines(DetailNode tagNode) { 212 int count = 0; 213 DetailNode currentNode = JavadocUtil.getPreviousSibling(tagNode); 214 while (currentNode != null 215 && (currentNode.getType() == JavadocTokenTypes.NEWLINE 216 || currentNode.getType() == JavadocTokenTypes.WS 217 || currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK)) { 218 if (currentNode.getType() == JavadocTokenTypes.NEWLINE) { 219 count++; 220 } 221 currentNode = JavadocUtil.getPreviousSibling(currentNode); 222 } 223 224 return count <= 1; 225 } 226}