View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.javadoc;
21  
22  import java.util.Locale;
23  
24  import com.puppycrawl.tools.checkstyle.StatelessCheck;
25  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
29  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
30  
31  /**
32   * <div>
33   * Checks that the Javadoc content begins from the same position
34   * for all Javadoc comments in the project. Any leading asterisks and spaces
35   * are not counted as the beginning of the content and are therefore ignored.
36   * </div>
37   *
38   * <p>
39   * It is possible to enforce two different styles:
40   * </p>
41   * <ul>
42   * <li>
43   * {@code first_line} - Javadoc content starts from the first line:
44   * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
45   * &#47;** Summary text.
46   *   * More details.
47   *   *&#47;
48   * public void method();
49   * </code></pre></div>
50   * </li>
51   * <li>
52   * {@code second_line} - Javadoc content starts from the second line:
53   * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
54   * &#47;**
55   *   * Summary text.
56   *   * More details.
57   *   *&#47;
58   * public void method();
59   * </code></pre></div>
60   * </li>
61   * </ul>
62   *
63   * <p>
64   * Notes:
65   * This check does not validate the Javadoc summary itself nor its presence.
66   * The check will not report any violations for missing or malformed javadoc summary.
67   * To validate the Javadoc summary use
68   * <a href="https://checkstyle.org/checks/javadoc/summaryjavadoc.html">
69   * SummaryJavadoc</a> check.
70   * </p>
71   *
72   * <p>
73   * The <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html">
74   * Documentation Comment Specification</a> permits leading asterisks on the first line.
75   * For these Javadoc comments:
76   * </p>
77   * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
78   * &#47;***
79   *   * Some text.
80   *   *&#47;
81   * &#47;************
82   *   * Some text.
83   *   *&#47;
84   * &#47;**           **
85   *   * Some text.
86   *   *&#47;
87   * </code></pre></div>
88   *
89   * <p>
90   * The documentation generated will be just "Some text." without any asterisks.
91   * Since these asterisks will not appear in the generated documentation,
92   * they should not be considered as the beginning of the Javadoc content.
93   * In such cases, the check assumes that the Javadoc content begins on the second line.
94   * </p>
95   *
96   * @since 8.27
97   */
98  @StatelessCheck
99  public class JavadocContentLocationCheck extends AbstractCheck {
100 
101     /**
102      * A key is pointing to the warning message text in "messages.properties" file.
103      */
104     public static final String MSG_JAVADOC_CONTENT_FIRST_LINE = "javadoc.content.first.line";
105 
106     /**
107      * A key is pointing to the warning message text in "messages.properties" file.
108      */
109     public static final String MSG_JAVADOC_CONTENT_SECOND_LINE = "javadoc.content.second.line";
110 
111     /**
112      * Specify the policy on placement of the Javadoc content.
113      */
114     private JavadocContentLocationOption location = JavadocContentLocationOption.SECOND_LINE;
115 
116     @Override
117     public int[] getRequiredTokens() {
118         return new int[] {
119             TokenTypes.BLOCK_COMMENT_BEGIN,
120         };
121     }
122 
123     @Override
124     public int[] getAcceptableTokens() {
125         return getRequiredTokens();
126     }
127 
128     @Override
129     public int[] getDefaultTokens() {
130         return getRequiredTokens();
131     }
132 
133     @Override
134     public boolean isCommentNodesRequired() {
135         return true;
136     }
137 
138     /**
139      * Setter to specify the policy on placement of the Javadoc content.
140      *
141      * @param value string to decode location from
142      * @throws IllegalArgumentException if unable to decode
143      * @since 8.27
144      */
145     public void setLocation(String value) {
146         location = JavadocContentLocationOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH));
147     }
148 
149     @Override
150     public void visitToken(DetailAST ast) {
151         if (isMultilineComment(ast) && JavadocUtil.isJavadocComment(ast)) {
152             final String commentContent = JavadocUtil.getJavadocCommentContent(ast);
153             final int indexOfFirstNonBlankLine = findIndexOfFirstNonBlankLine(commentContent);
154             if (indexOfFirstNonBlankLine >= 0) {
155                 if (location == JavadocContentLocationOption.FIRST_LINE
156                         && indexOfFirstNonBlankLine != 0) {
157                     log(ast, MSG_JAVADOC_CONTENT_FIRST_LINE);
158                 }
159                 else if (location == JavadocContentLocationOption.SECOND_LINE
160                         && indexOfFirstNonBlankLine != 1) {
161                     log(ast, MSG_JAVADOC_CONTENT_SECOND_LINE);
162                 }
163             }
164         }
165     }
166 
167     /**
168      * Checks if a DetailAST of type {@code TokenTypes.BLOCK_COMMENT_BEGIN} span
169      * more than one line. The node always has at least one child of the type
170      * {@code TokenTypes.BLOCK_COMMENT_END}.
171      *
172      * @param node node to check
173      * @return {@code true} for multi-line comment nodes
174      */
175     private static boolean isMultilineComment(DetailAST node) {
176         return !TokenUtil.areOnSameLine(node, node.getLastChild());
177     }
178 
179     /**
180      * Returns the index of the first non-blank line.
181      * All lines consists only of asterisks and whitespaces are treated as blank.
182      *
183      * @param commentContent Javadoc content to process
184      * @return the index of the first non-blank line or {@code -1} if all lines are blank
185      */
186     private static int findIndexOfFirstNonBlankLine(String commentContent) {
187         int lineNo = 0;
188         boolean noContent = true;
189         for (int i = 0; i < commentContent.length(); ++i) {
190             final char character = commentContent.charAt(i);
191             if (character == '\n') {
192                 ++lineNo;
193             }
194             else if (character != '*' && !Character.isWhitespace(character)) {
195                 noContent = false;
196                 break;
197             }
198         }
199         if (noContent) {
200             lineNo = -1;
201         }
202         return lineNo;
203     }
204 
205 }