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   * Prohibit a trailing dot in the description of {@code @param} and
13   * {@code @return} Javadoc tags.
14   *
15   * <p>For consistency, descriptions of these tags must not end with
16   * a period.</p>
17   *
18   * <p>Valid:</p>
19   *
20   * <pre>
21   * &#47;**
22   *  * &#64;param text A string with contents. Cannot be null
23   *  * &#64;return True when empty, false otherwise
24   *  *&#47;
25   * </pre>
26   *
27   * <p>Invalid:</p>
28   *
29   * <pre>
30   * &#47;**
31   *  * &#64;param text A string with contents. Cannot be null.
32   *  * &#64;return True when empty, false otherwise.
33   *  *&#47;
34   * </pre>
35   *
36   * @since 0.24.1
37   */
38  public final class JavadocTagsDotCheck extends AbstractCheck {
39  
40      /**
41       * Default constructor.
42       */
43      public JavadocTagsDotCheck() {
44          // nothing to initialize
45      }
46  
47      @Override
48      public int[] getDefaultTokens() {
49          return new int[] {
50              TokenTypes.METHOD_DEF,
51              TokenTypes.CTOR_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 cend = JavadocTagsDotCheck.findTrimmedTextUp(
69              lines, ast.getLineNo() - 1, "*/"
70          );
71          final int cstart = JavadocTagsDotCheck.findTrimmedTextUp(
72              lines, cend, "/**"
73          );
74          if (cstart >= 0 && cend > cstart) {
75              this.inspect(lines, cstart, cend);
76          }
77      }
78  
79      private void inspect(final String[] lines, final int cstart, final int cend) {
80          int tag = -1;
81          for (int pos = cstart + 1; pos <= cend; pos += 1) {
82              final String trimmed = lines[pos].trim();
83              final boolean next = trimmed.startsWith("* @")
84                  || "*/".equals(trimmed);
85              if (next && tag >= 0) {
86                  this.verify(lines, tag, pos - 1);
87                  tag = -1;
88              }
89              if (JavadocTagsDotCheck.isParamOrReturn(trimmed)) {
90                  tag = pos;
91              }
92          }
93      }
94  
95      private void verify(final String[] lines, final int from, final int until) {
96          for (int pos = until; pos >= from; pos -= 1) {
97              final String content = JavadocTagsDotCheck.stripMarker(lines[pos]);
98              if (!content.isEmpty()) {
99                  if (content.endsWith(".")) {
100                     this.log(
101                         pos + 1,
102                         "No dot allowed at the end of a '@param' or '@return' Javadoc tag"
103                     );
104                 }
105                 break;
106             }
107         }
108     }
109 
110     private static boolean isParamOrReturn(final String trimmed) {
111         final String tag;
112         if (trimmed.startsWith("* @")) {
113             tag = trimmed.substring("* @".length());
114         } else {
115             tag = "";
116         }
117         return tag.startsWith("param ")
118             || tag.startsWith("return ")
119             || "return".equals(tag);
120     }
121 
122     private static String stripMarker(final String line) {
123         String result = line.trim();
124         if (result.startsWith("*")) {
125             result = result.substring(1).trim();
126         }
127         return result;
128     }
129 
130     private static int findTrimmedTextUp(
131         final String[] lines,
132         final int start,
133         final String text
134     ) {
135         int found = -1;
136         for (int pos = start - 1; pos >= 0; pos -= 1) {
137             if (lines[pos].trim().equals(text)) {
138                 found = pos;
139                 break;
140             }
141         }
142         return found;
143     }
144 }