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 text on the first line of a multi-line Javadoc.
13   *
14   * <p>You can't have a description on the same line as the opening
15   * {@code /**}. Either keep the whole Javadoc on a single line, or move
16   * the text to a new line under the opening.
17   *
18   * <p>The following red line will be reported as a violation.
19   * <pre>
20   * <span style="color:red" >&#47;** Some text</span>
21   *  *&#47;
22   * public void method() {
23   * }
24   * </pre>
25   *
26   * @since 0.24.1
27   */
28  public final class JavadocFirstLineCheck extends AbstractCheck {
29  
30      @Override
31      public int[] getDefaultTokens() {
32          return new int[] {
33              TokenTypes.PACKAGE_DEF,
34              TokenTypes.CLASS_DEF,
35              TokenTypes.INTERFACE_DEF,
36              TokenTypes.ANNOTATION_DEF,
37              TokenTypes.ANNOTATION_FIELD_DEF,
38              TokenTypes.ENUM_DEF,
39              TokenTypes.ENUM_CONSTANT_DEF,
40              TokenTypes.VARIABLE_DEF,
41              TokenTypes.CTOR_DEF,
42              TokenTypes.METHOD_DEF,
43          };
44      }
45  
46      @Override
47      public int[] getAcceptableTokens() {
48          return this.getDefaultTokens();
49      }
50  
51      @Override
52      public int[] getRequiredTokens() {
53          return this.getDefaultTokens();
54      }
55  
56      @Override
57      public void visitToken(final DetailAST ast) {
58          final String[] lines = this.getLines();
59          final int start = JavadocFirstLineCheck.findOpeningLine(
60              lines, ast.getLineNo() - 1
61          );
62          if (start >= 0 && JavadocFirstLineCheck.belongsToNode(ast, start)
63              && JavadocFirstLineCheck.hasTextAfterOpening(lines[start])
64              && !JavadocFirstLineCheck.hasClosingOnSameLine(lines[start])) {
65              this.log(start + 1, "No text allowed on the first line of Javadoc");
66          }
67      }
68  
69      /**
70       * Find the line that opens the Javadoc right above a node.
71       * @param lines All lines of the file
72       * @param below Line index of the node (0-based)
73       * @return Line index (0-based) of the opening, or -1 if not found
74       */
75      private static int findOpeningLine(final String[] lines, final int below) {
76          int found = -1;
77          for (int pos = below - 1; pos >= 0; pos -= 1) {
78              final String trimmed = lines[pos].trim();
79              if (trimmed.startsWith("/**")) {
80                  found = pos;
81                  break;
82              }
83              if (!trimmed.isEmpty() && !trimmed.startsWith("*")
84                  && !trimmed.endsWith("*/")) {
85                  break;
86              }
87          }
88          return found;
89      }
90  
91      /**
92       * Check that the found Javadoc directly precedes the node.
93       * @param node Node being inspected
94       * @param start Line index (0-based) of the Javadoc opening
95       * @return True when no other declaration sits between
96       */
97      private static boolean belongsToNode(final DetailAST node, final int start) {
98          final DetailAST previous = node.getPreviousSibling();
99          boolean owns = true;
100         if (previous != null) {
101             owns = start + 1 > previous.getLineNo();
102         }
103         return owns;
104     }
105 
106     /**
107      * Check if a line has any text after the opening {@code /**}.
108      * @param line The opening line
109      * @return True when text sits on the same line
110      */
111     private static boolean hasTextAfterOpening(final String line) {
112         final String trimmed = line.trim();
113         final String rest = trimmed.substring("/**".length()).trim();
114         return !rest.isEmpty() && !"/".equals(rest);
115     }
116 
117     /**
118      * Check if the Javadoc closes on the same line.
119      * @param line The opening line
120      * @return True when {@code *&#47;} is on the same line
121      */
122     private static boolean hasClosingOnSameLine(final String line) {
123         return line.trim().endsWith("*/");
124     }
125 }