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   * <ul>
96   * <li>
97   * Property {@code location} - Specify the policy on placement of the Javadoc content.
98   * Type is {@code com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocContentLocationOption}.
99   * Default value is {@code second_line}.
100  * </li>
101  * </ul>
102  *
103  * <p>
104  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
105  * </p>
106  *
107  * <p>
108  * Violation Message Keys:
109  * </p>
110  * <ul>
111  * <li>
112  * {@code javadoc.content.first.line}
113  * </li>
114  * <li>
115  * {@code javadoc.content.second.line}
116  * </li>
117  * </ul>
118  *
119  * @since 8.27
120  */
121 @StatelessCheck
122 public class JavadocContentLocationCheck extends AbstractCheck {
123 
124     /**
125      * A key is pointing to the warning message text in "messages.properties" file.
126      */
127     public static final String MSG_JAVADOC_CONTENT_FIRST_LINE = "javadoc.content.first.line";
128 
129     /**
130      * A key is pointing to the warning message text in "messages.properties" file.
131      */
132     public static final String MSG_JAVADOC_CONTENT_SECOND_LINE = "javadoc.content.second.line";
133 
134     /**
135      * Specify the policy on placement of the Javadoc content.
136      */
137     private JavadocContentLocationOption location = JavadocContentLocationOption.SECOND_LINE;
138 
139     @Override
140     public int[] getRequiredTokens() {
141         return new int[] {
142             TokenTypes.BLOCK_COMMENT_BEGIN,
143         };
144     }
145 
146     @Override
147     public int[] getAcceptableTokens() {
148         return getRequiredTokens();
149     }
150 
151     @Override
152     public int[] getDefaultTokens() {
153         return getRequiredTokens();
154     }
155 
156     @Override
157     public boolean isCommentNodesRequired() {
158         return true;
159     }
160 
161     /**
162      * Setter to specify the policy on placement of the Javadoc content.
163      *
164      * @param value string to decode location from
165      * @throws IllegalArgumentException if unable to decode
166      * @since 8.27
167      */
168     public void setLocation(String value) {
169         location = JavadocContentLocationOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH));
170     }
171 
172     @Override
173     public void visitToken(DetailAST ast) {
174         if (isMultilineComment(ast) && JavadocUtil.isJavadocComment(ast)) {
175             final String commentContent = JavadocUtil.getJavadocCommentContent(ast);
176             final int indexOfFirstNonBlankLine = findIndexOfFirstNonBlankLine(commentContent);
177             if (indexOfFirstNonBlankLine >= 0) {
178                 if (location == JavadocContentLocationOption.FIRST_LINE
179                         && indexOfFirstNonBlankLine != 0) {
180                     log(ast, MSG_JAVADOC_CONTENT_FIRST_LINE);
181                 }
182                 else if (location == JavadocContentLocationOption.SECOND_LINE
183                         && indexOfFirstNonBlankLine != 1) {
184                     log(ast, MSG_JAVADOC_CONTENT_SECOND_LINE);
185                 }
186             }
187         }
188     }
189 
190     /**
191      * Checks if a DetailAST of type {@code TokenTypes.BLOCK_COMMENT_BEGIN} span
192      * more than one line. The node always has at least one child of the type
193      * {@code TokenTypes.BLOCK_COMMENT_END}.
194      *
195      * @param node node to check
196      * @return {@code true} for multi-line comment nodes
197      */
198     private static boolean isMultilineComment(DetailAST node) {
199         return !TokenUtil.areOnSameLine(node, node.getLastChild());
200     }
201 
202     /**
203      * Returns the index of the first non-blank line.
204      * All lines consists only of asterisks and whitespaces are treated as blank.
205      *
206      * @param commentContent Javadoc content to process
207      * @return the index of the first non-blank line or {@code -1} if all lines are blank
208      */
209     private static int findIndexOfFirstNonBlankLine(String commentContent) {
210         int lineNo = 0;
211         boolean noContent = true;
212         for (int i = 0; i < commentContent.length(); ++i) {
213             final char character = commentContent.charAt(i);
214             if (character == '\n') {
215                 ++lineNo;
216             }
217             else if (character != '*' && !Character.isWhitespace(character)) {
218                 noContent = false;
219                 break;
220             }
221         }
222         if (noContent) {
223             lineNo = -1;
224         }
225         return lineNo;
226     }
227 
228 }