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  import org.cactoos.text.Sub;
11  
12  /**
13   * Check indents in multi line JavaDoc tags.
14   *
15   * <p>This is how you should format javadoc tags that need a few lines:</p>
16   *
17   * <pre>
18   * &#47;**
19   *  * This is my new method.
20   *  * &#64;param text Some text information, provided to the
21   *  *  method by another class
22   *  * &#64;todo #123 I will implement it later, when more information
23   *  *  come to light and I have documentation supplied by
24   *  *  AAA team in the office across the street
25   *  *&#47;
26   * public void func() {
27   *     // ...
28   * }
29   * </pre>
30   *
31   * <p>Keep in mind that all free-text information should go <b>before</b>
32   * javadoc tags, or else it will treated as part of the latest tag and
33   * qulice will complain.</p>
34   *
35   * @since 0.3
36   */
37  public final class MultilineJavadocTagsCheck extends AbstractCheck {
38  
39      /**
40       * Default constructor.
41       */
42      public MultilineJavadocTagsCheck() {
43          // nothing to initialize
44      }
45  
46      @Override
47      public int[] getDefaultTokens() {
48          return new int[] {
49              TokenTypes.METHOD_DEF,
50              TokenTypes.CTOR_DEF,
51              TokenTypes.PACKAGE_DEF,
52          };
53      }
54  
55      @Override
56      public int[] getAcceptableTokens() {
57          return this.getDefaultTokens();
58      }
59  
60      @Override
61      public int[] getRequiredTokens() {
62          return this.getDefaultTokens();
63      }
64  
65      @Override
66      public void visitToken(final DetailAST ast) {
67          final String[] lines = this.getLines();
68          final int start = ast.getLineNo();
69          final int cstart =
70              MultilineJavadocTagsCheck.findCommentStart(lines, start) + 1;
71          final int cend =
72              MultilineJavadocTagsCheck.findCommentEnd(lines, start) - 1;
73          if (cend >= cstart && cstart >= 0) {
74              this.checkJavaDoc(lines, cstart, cend);
75          } else {
76              this.log(0, "Can't find method comment");
77          }
78      }
79  
80      @SuppressWarnings("PMD.InefficientEmptyStringCheck")
81      private void checkJavaDoc(final String[] lines, final int start,
82          final int end) {
83          boolean tagged = false;
84          int index = -1;
85          for (int current = start; current <= end; current += 1) {
86              final String line = lines[current];
87              if (line.contains("* @")) {
88                  tagged = true;
89                  index = line.indexOf('@');
90              } else if (tagged) {
91                  final int comment = line.indexOf('*');
92                  final String sub = new Sub(
93                      line, comment + 1, index + 1
94                  ).toString();
95                  final String ext = new Sub(
96                      line, comment + 1, index + 2
97                  ).toString();
98                  if (!sub.trim().isEmpty() || ext.trim().isEmpty()) {
99                      this.log(
100                         current + 1,
101                         "Should contain one indentation space"
102                     );
103                 }
104             }
105         }
106     }
107 
108     private static int findCommentStart(final String[] lines, final int start) {
109         return Math.max(
110             MultilineJavadocTagsCheck.findTrimmedTextUp(lines, start, "/**"),
111             MultilineJavadocTagsCheck.findTrimmedTextUp(lines, start, "/*")
112         );
113     }
114 
115     private static int findCommentEnd(final String[] lines, final int start) {
116         return MultilineJavadocTagsCheck.findTrimmedTextUp(lines, start, "*/");
117     }
118 
119     private static int findTrimmedTextUp(final String[] lines,
120         final int start, final String text) {
121         int found = -1;
122         for (int pos = start - 1; pos >= 0; pos -= 1) {
123             if (lines[pos].trim().equals(text)) {
124                 found = pos;
125                 break;
126             }
127         }
128         return found;
129     }
130 }