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