View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  
11  /**
12   * Check for empty lines inside Javadoc.
13   *
14   * <p>You can't have an empty line at the beginning or at the end of Javadoc,
15   * and two consecutive empty lines are not allowed anywhere inside it.
16   *
17   * <p>The following red lines in class Javadoc will be reported as violations.
18   * <pre>
19   * &#47;**
20   *  <span style="color:red" >*</span>
21   *  * This is my class.
22   *  *
23   *  <span style="color:red" >*</span>
24   *  * More text.
25   *  <span style="color:red" >*</span>
26   *  *&#47;
27   * public final class Foo {
28   *     // ...
29   * </pre>
30   *
31   * @since 0.17
32   */
33  public final class JavadocEmptyLineCheck extends AbstractCheck {
34  
35      @Override
36      public int[] getDefaultTokens() {
37          return new int[] {
38              TokenTypes.PACKAGE_DEF,
39              TokenTypes.CLASS_DEF,
40              TokenTypes.INTERFACE_DEF,
41              TokenTypes.ANNOTATION_DEF,
42              TokenTypes.ANNOTATION_FIELD_DEF,
43              TokenTypes.ENUM_DEF,
44              TokenTypes.ENUM_CONSTANT_DEF,
45              TokenTypes.VARIABLE_DEF,
46              TokenTypes.CTOR_DEF,
47              TokenTypes.METHOD_DEF,
48          };
49      }
50  
51      @Override
52      public int[] getAcceptableTokens() {
53          return this.getDefaultTokens();
54      }
55  
56      @Override
57      public int[] getRequiredTokens() {
58          return this.getDefaultTokens();
59      }
60  
61      @Override
62      public void visitToken(final DetailAST ast) {
63          final String[] lines = this.getLines();
64          final int current = ast.getLineNo();
65          final int start =
66              JavadocEmptyLineCheck.findCommentStart(lines, current) + 1;
67          if (JavadocEmptyLineCheck.isNodeHavingJavadoc(ast, start)
68              && start < lines.length) {
69              if (JavadocEmptyLineCheck.isJavadocLineEmpty(lines[start])) {
70                  this.log(start + 1, "Empty Javadoc line at the beginning");
71              }
72              final int end =
73                  JavadocEmptyLineCheck.findCommentEnd(lines, current) - 1;
74              if (end >= start
75                  && JavadocEmptyLineCheck.isJavadocLineEmpty(lines[end])) {
76                  this.log(end + 1, "Empty Javadoc line at the end");
77              }
78              for (int pos = start + 1; pos <= end; pos += 1) {
79                  if (JavadocEmptyLineCheck.isJavadocLineEmpty(lines[pos])
80                      && JavadocEmptyLineCheck.isJavadocLineEmpty(lines[pos - 1])
81                  ) {
82                      this.log(pos + 1, "Two consecutive empty Javadoc lines");
83                  }
84              }
85          }
86      }
87  
88      /**
89       * Check if Javadoc line is empty.
90       * @param line Javadoc line
91       * @return True when Javadoc line is empty
92       */
93      private static boolean isJavadocLineEmpty(final String line) {
94          return "*".equals(line.trim());
95      }
96  
97      /**
98       * Check if node has Javadoc.
99       * @param node Node to be checked for Javadoc
100      * @param start Line number where comment starts
101      * @return True when node has Javadoc
102      */
103     private static boolean isNodeHavingJavadoc(final DetailAST node,
104         final int start) {
105         return start > getLineNoOfPreviousNode(node);
106     }
107 
108     /**
109      * Returns line number of previous node.
110      * @param node Current node
111      * @return Line number of previous node
112      */
113     private static int getLineNoOfPreviousNode(final DetailAST node) {
114         int start = 0;
115         final DetailAST previous = node.getPreviousSibling();
116         if (previous != null) {
117             start = previous.getLineNo();
118         }
119         return start;
120     }
121 
122     /**
123      * Find Javadoc starting comment.
124      * @param lines List of lines to check
125      * @param start Start searching from this line number
126      * @return Line number with found starting comment or -1 otherwise
127      */
128     private static int findCommentStart(final String[] lines, final int start) {
129         return JavadocEmptyLineCheck.findTrimmedTextUp(lines, start, "/**");
130     }
131 
132     /**
133      * Find Javadoc ending comment.
134      * @param lines Array of lines to check
135      * @param start Start searching from this line number
136      * @return Line number with found ending comment, or -1 if it wasn't found
137      */
138     private static int findCommentEnd(final String[] lines, final int start) {
139         int found = -1;
140         for (int pos = start - 1; pos >= 0; pos -= 1) {
141             final String trimmed = lines[pos].trim();
142             if ("*/".equals(trimmed) || "**/".equals(trimmed)) {
143                 found = pos;
144                 break;
145             }
146         }
147         return found;
148     }
149 
150     /**
151      * Find a text in lines, by going up.
152      * @param lines Array of lines to check
153      * @param start Start searching from this line number
154      * @param text Text to find
155      * @return Line number with found text, or -1 if it wasn't found
156      */
157     private static int findTrimmedTextUp(final String[] lines,
158         final int start, final String text) {
159         int found = -1;
160         for (int pos = start - 1; pos >= 0; pos -= 1) {
161             if (lines[pos].trim().equals(text)) {
162                 found = pos;
163                 break;
164             }
165         }
166         return found;
167     }
168 }