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.</p>
17   *
18   * <p>The following red line will be reported as a violation.</p>
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      /**
31       * Default constructor.
32       */
33      public JavadocFirstLineCheck() {
34          // nothing to initialize
35      }
36  
37      @Override
38      public int[] getDefaultTokens() {
39          return new int[] {
40              TokenTypes.PACKAGE_DEF,
41              TokenTypes.CLASS_DEF,
42              TokenTypes.INTERFACE_DEF,
43              TokenTypes.ANNOTATION_DEF,
44              TokenTypes.ANNOTATION_FIELD_DEF,
45              TokenTypes.ENUM_DEF,
46              TokenTypes.ENUM_CONSTANT_DEF,
47              TokenTypes.VARIABLE_DEF,
48              TokenTypes.CTOR_DEF,
49              TokenTypes.METHOD_DEF,
50          };
51      }
52  
53      @Override
54      public int[] getAcceptableTokens() {
55          return this.getDefaultTokens();
56      }
57  
58      @Override
59      public int[] getRequiredTokens() {
60          return this.getDefaultTokens();
61      }
62  
63      @Override
64      public void visitToken(final DetailAST ast) {
65          final String[] lines = this.getLines();
66          final int start = JavadocFirstLineCheck.findOpeningLine(
67              lines, ast.getLineNo() - 1
68          );
69          if (start >= 0 && JavadocFirstLineCheck.belongsToNode(ast, start)
70              && JavadocFirstLineCheck.hasTextAfterOpening(lines[start])
71              && !JavadocFirstLineCheck.hasClosingOnSameLine(lines[start])) {
72              this.log(start + 1, "No text allowed on the first line of Javadoc");
73          }
74      }
75  
76      private static int findOpeningLine(final String[] lines, final int below) {
77          int found = -1;
78          for (int pos = below - 1; pos >= 0; pos -= 1) {
79              final String trimmed = lines[pos].trim();
80              if (trimmed.startsWith("/**")) {
81                  found = pos;
82                  break;
83              }
84              if (!trimmed.isEmpty() && !trimmed.startsWith("*")
85                  && !trimmed.endsWith("*/")) {
86                  break;
87              }
88          }
89          return found;
90      }
91  
92      private static boolean belongsToNode(final DetailAST node, final int start) {
93          final DetailAST previous = node.getPreviousSibling();
94          boolean owns = true;
95          if (previous != null) {
96              owns = start + 1 > previous.getLineNo();
97          }
98          return owns;
99      }
100 
101     private static boolean hasTextAfterOpening(final String line) {
102         final String trimmed = line.trim();
103         final String rest = trimmed.substring("/**".length()).trim();
104         return !rest.isEmpty() && !"/".equals(rest);
105     }
106 
107     private static boolean hasClosingOnSameLine(final String line) {
108         return line.trim().endsWith("*/");
109     }
110 }