View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <pre>
45   * &#47;** Summary text.
46   *   * More details.
47   *   *&#47;
48   * public void method();
49   * </pre>
50   * </li>
51   * <li>
52   * {@code second_line} - Javadoc content starts from the second line:
53   * <pre>
54   * &#47;**
55   *   * Summary text.
56   *   * More details.
57   *   *&#47;
58   * public void method();
59   * </pre>
60   * </li>
61   * </ul>
62   *
63   * <p>
64   * This check does not validate the Javadoc summary itself nor its presence.
65   * The check will not report any violations for missing or malformed javadoc summary.
66   * To validate the Javadoc summary use
67   * <a href="https://checkstyle.org/checks/javadoc/summaryjavadoc.html#SummaryJavadoc">
68   * SummaryJavadoc</a> check.
69   * </p>
70   *
71   * <p>
72   * The <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html">
73   * Documentation Comment Specification</a> permits leading asterisks on the first line.
74   * For these Javadoc comments:
75   * </p>
76   * <pre>
77   * &#47;***
78   *   * Some text.
79   *   *&#47;
80   * &#47;************
81   *   * Some text.
82   *   *&#47;
83   * &#47;**           **
84   *   * Some text.
85   *   *&#47;
86   * </pre>
87   *
88   * <p>
89   * The documentation generated will be just "Some text." without any asterisks.
90   * Since these asterisks will not appear in the generated documentation,
91   * they should not be considered as the beginning of the Javadoc content.
92   * In such cases, the check assumes that the Javadoc content begins on the second line.
93   * </p>
94   * <ul>
95   * <li>
96   * Property {@code location} - Specify the policy on placement of the Javadoc content.
97   * Type is {@code com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocContentLocationOption}.
98   * Default value is {@code second_line}.
99   * </li>
100  * </ul>
101  *
102  * <p>
103  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
104  * </p>
105  *
106  * <p>
107  * Violation Message Keys:
108  * </p>
109  * <ul>
110  * <li>
111  * {@code javadoc.content.first.line}
112  * </li>
113  * <li>
114  * {@code javadoc.content.second.line}
115  * </li>
116  * </ul>
117  *
118  * @since 8.27
119  */
120 @StatelessCheck
121 public class JavadocContentLocationCheck extends AbstractCheck {
122 
123     /**
124      * A key is pointing to the warning message text in "messages.properties" file.
125      */
126     public static final String MSG_JAVADOC_CONTENT_FIRST_LINE = "javadoc.content.first.line";
127 
128     /**
129      * A key is pointing to the warning message text in "messages.properties" file.
130      */
131     public static final String MSG_JAVADOC_CONTENT_SECOND_LINE = "javadoc.content.second.line";
132 
133     /**
134      * Specify the policy on placement of the Javadoc content.
135      */
136     private JavadocContentLocationOption location = JavadocContentLocationOption.SECOND_LINE;
137 
138     @Override
139     public int[] getRequiredTokens() {
140         return new int[] {
141             TokenTypes.BLOCK_COMMENT_BEGIN,
142         };
143     }
144 
145     @Override
146     public int[] getAcceptableTokens() {
147         return getRequiredTokens();
148     }
149 
150     @Override
151     public int[] getDefaultTokens() {
152         return getRequiredTokens();
153     }
154 
155     @Override
156     public boolean isCommentNodesRequired() {
157         return true;
158     }
159 
160     /**
161      * Setter to specify the policy on placement of the Javadoc content.
162      *
163      * @param value string to decode location from
164      * @throws IllegalArgumentException if unable to decode
165      * @since 8.27
166      */
167     public void setLocation(String value) {
168         location = JavadocContentLocationOption.valueOf(value.trim().toUpperCase(Locale.ENGLISH));
169     }
170 
171     @Override
172     public void visitToken(DetailAST ast) {
173         if (isMultilineComment(ast) && JavadocUtil.isJavadocComment(ast)) {
174             final String commentContent = JavadocUtil.getJavadocCommentContent(ast);
175             final int indexOfFirstNonBlankLine = findIndexOfFirstNonBlankLine(commentContent);
176             if (indexOfFirstNonBlankLine >= 0) {
177                 if (location == JavadocContentLocationOption.FIRST_LINE
178                         && indexOfFirstNonBlankLine != 0) {
179                     log(ast, MSG_JAVADOC_CONTENT_FIRST_LINE);
180                 }
181                 else if (location == JavadocContentLocationOption.SECOND_LINE
182                         && indexOfFirstNonBlankLine != 1) {
183                     log(ast, MSG_JAVADOC_CONTENT_SECOND_LINE);
184                 }
185             }
186         }
187     }
188 
189     /**
190      * Checks if a DetailAST of type {@code TokenTypes.BLOCK_COMMENT_BEGIN} span
191      * more than one line. The node always has at least one child of the type
192      * {@code TokenTypes.BLOCK_COMMENT_END}.
193      *
194      * @param node node to check
195      * @return {@code true} for multi-line comment nodes
196      */
197     private static boolean isMultilineComment(DetailAST node) {
198         return !TokenUtil.areOnSameLine(node, node.getLastChild());
199     }
200 
201     /**
202      * Returns the index of the first non-blank line.
203      * All lines consists only of asterisks and whitespaces are treated as blank.
204      *
205      * @param commentContent Javadoc content to process
206      * @return the index of the first non-blank line or {@code -1} if all lines are blank
207      */
208     private static int findIndexOfFirstNonBlankLine(String commentContent) {
209         int lineNo = 0;
210         boolean noContent = true;
211         for (int i = 0; i < commentContent.length(); ++i) {
212             final char character = commentContent.charAt(i);
213             if (character == '\n') {
214                 ++lineNo;
215             }
216             else if (character != '*' && !Character.isWhitespace(character)) {
217                 noContent = false;
218                 break;
219             }
220         }
221         if (noContent) {
222             lineNo = -1;
223         }
224         return lineNo;
225     }
226 
227 }