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