View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.javadoc;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.List;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailNode;
28  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
29  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
30  
31  /**
32   * <p>
33   * Checks that one blank line before the block tag if it is present in Javadoc.
34   * </p>
35   * <ul>
36   * <li>
37   * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
38   * if the Javadoc being examined by this check violates the tight html rules defined at
39   * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
40   * Tight-HTML Rules</a>.
41   * Type is {@code boolean}.
42   * Default value is {@code false}.
43   * </li>
44   * </ul>
45   * <p>
46   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
47   * </p>
48   * <p>
49   * Violation Message Keys:
50   * </p>
51   * <ul>
52   * <li>
53   * {@code javadoc.missed.html.close}
54   * </li>
55   * <li>
56   * {@code javadoc.parse.rule.error}
57   * </li>
58   * <li>
59   * {@code javadoc.tag.line.before}
60   * </li>
61   * <li>
62   * {@code javadoc.unclosedHtml}
63   * </li>
64   * <li>
65   * {@code javadoc.wrong.singleton.html.tag}
66   * </li>
67   * </ul>
68   *
69   * @since 8.36
70   */
71  @StatelessCheck
72  public class RequireEmptyLineBeforeBlockTagGroupCheck extends AbstractJavadocCheck {
73  
74      /**
75       * The key in "messages.properties" for the message that describes a tag in javadoc
76       * requiring an empty line before it.
77       */
78      public static final String MSG_JAVADOC_TAG_LINE_BEFORE = "javadoc.tag.line.before";
79  
80      /**
81       * Case when space separates the tag and the asterisk like in the below example.
82       * <pre>
83       *  /**
84       *   * &#64;param noSpace there is no space here
85       * </pre>
86       */
87      private static final List<Integer> ONLY_TAG_VARIATION_1 = Arrays.asList(
88              JavadocTokenTypes.WS,
89              JavadocTokenTypes.LEADING_ASTERISK,
90              JavadocTokenTypes.NEWLINE);
91  
92      /**
93       * Case when no space separates the tag and the asterisk like in the below example.
94       * <pre>
95       *  /**
96       *   *&#64;param noSpace there is no space here
97       * </pre>
98       */
99      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      *  /**&#64;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 &#64;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 }