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