View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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     /**
117      * Creates a new {@code JavadocContentLocationCheck} instance.
118      */
119     public JavadocContentLocationCheck() {
120         // no code by default
121     }
122 
123     @Override
124     public int[] getRequiredTokens() {
125         return new int[] {
126             TokenTypes.BLOCK_COMMENT_BEGIN,
127         };
128     }
129 
130     @Override
131     public int[] getAcceptableTokens() {
132         return getRequiredTokens();
133     }
134 
135     @Override
136     public int[] getDefaultTokens() {
137         return getRequiredTokens();
138     }
139 
140     @Override
141     public boolean isCommentNodesRequired() {
142         return true;
143     }
144 
145     /**
146      * Setter to specify the policy on placement of the Javadoc content.
147      *
148      * @param value string to decode location from
149      * @throws IllegalArgumentException if unable to decode
150      * @since 8.27
151      */
152     public void setLocation(String value) {
153         location = JavadocContentLocationOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH));
154     }
155 
156     @Override
157     public void visitToken(DetailAST ast) {
158         if (isMultilineComment(ast) && JavadocUtil.isJavadocComment(ast)) {
159             final String commentContent = JavadocUtil.getJavadocCommentContent(ast);
160             final int indexOfFirstNonBlankLine = findIndexOfFirstNonBlankLine(commentContent);
161             if (indexOfFirstNonBlankLine >= 0) {
162                 if (location == JavadocContentLocationOption.FIRST_LINE
163                         && indexOfFirstNonBlankLine != 0) {
164                     log(ast, MSG_JAVADOC_CONTENT_FIRST_LINE);
165                 }
166                 else if (location == JavadocContentLocationOption.SECOND_LINE
167                         && indexOfFirstNonBlankLine != 1) {
168                     log(ast, MSG_JAVADOC_CONTENT_SECOND_LINE);
169                 }
170             }
171         }
172     }
173 
174     /**
175      * Checks if a DetailAST of type {@code TokenTypes.BLOCK_COMMENT_BEGIN} span
176      * more than one line. The node always has at least one child of the type
177      * {@code TokenTypes.BLOCK_COMMENT_END}.
178      *
179      * @param node node to check
180      * @return {@code true} for multi-line comment nodes
181      */
182     private static boolean isMultilineComment(DetailAST node) {
183         return !TokenUtil.areOnSameLine(node, node.getLastChild());
184     }
185 
186     /**
187      * Returns the index of the first non-blank line.
188      * All lines consists only of asterisks and whitespaces are treated as blank.
189      *
190      * @param commentContent Javadoc content to process
191      * @return the index of the first non-blank line or {@code -1} if all lines are blank
192      */
193     private static int findIndexOfFirstNonBlankLine(String commentContent) {
194         int lineNo = 0;
195         boolean noContent = true;
196         for (int i = 0; i < commentContent.length(); i++) {
197             final char character = commentContent.charAt(i);
198             if (character == '\n') {
199                 ++lineNo;
200             }
201             else if (character != '*' && !Character.isWhitespace(character)) {
202                 noContent = false;
203                 break;
204             }
205         }
206         if (noContent) {
207             lineNo = -1;
208         }
209         return lineNo;
210     }
211 
212 }