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.ArrayList;
23  import java.util.List;
24  
25  import com.puppycrawl.tools.checkstyle.StatelessCheck;
26  import com.puppycrawl.tools.checkstyle.api.DetailNode;
27  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
28  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
29  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
30  
31  /**
32   * <div>
33   * Checks the indentation of the continuation lines in block tags. That is whether the continued
34   * description of at clauses should be indented or not. If the text is not properly indented it
35   * throws a violation. A continuation line is when the description starts/spans past the line with
36   * the tag. Default indentation required is at least 4, but this can be changed with the help of
37   * properties below.
38   * </div>
39   * <ul>
40   * <li>
41   * Property {@code offset} - Specify how many spaces to use for new indentation level.
42   * Type is {@code int}.
43   * Default value is {@code 4}.
44   * </li>
45   * <li>
46   * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
47   * if the Javadoc being examined by this check violates the tight html rules defined at
48   * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
49   * Type is {@code boolean}.
50   * Default value is {@code false}.
51   * </li>
52   * </ul>
53   *
54   * <p>
55   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
56   * </p>
57   *
58   * <p>
59   * Violation Message Keys:
60   * </p>
61   * <ul>
62   * <li>
63   * {@code javadoc.missed.html.close}
64   * </li>
65   * <li>
66   * {@code javadoc.parse.rule.error}
67   * </li>
68   * <li>
69   * {@code javadoc.unclosedHtml}
70   * </li>
71   * <li>
72   * {@code javadoc.wrong.singleton.html.tag}
73   * </li>
74   * <li>
75   * {@code tag.continuation.indent}
76   * </li>
77   * </ul>
78   *
79   * @since 6.0
80   *
81   */
82  @StatelessCheck
83  public class JavadocTagContinuationIndentationCheck extends AbstractJavadocCheck {
84  
85      /**
86       * A key is pointing to the warning message text in "messages.properties"
87       * file.
88       */
89      public static final String MSG_KEY = "tag.continuation.indent";
90  
91      /** Default tag continuation indentation. */
92      private static final int DEFAULT_INDENTATION = 4;
93  
94      /**
95       * Specify how many spaces to use for new indentation level.
96       */
97      private int offset = DEFAULT_INDENTATION;
98  
99      /**
100      * Setter to specify how many spaces to use for new indentation level.
101      *
102      * @param offset custom value.
103      * @since 6.0
104      */
105     public void setOffset(int offset) {
106         this.offset = offset;
107     }
108 
109     @Override
110     public int[] getDefaultJavadocTokens() {
111         return new int[] {JavadocTokenTypes.HTML_TAG, JavadocTokenTypes.DESCRIPTION};
112 
113     }
114 
115     @Override
116     public int[] getRequiredJavadocTokens() {
117         return getAcceptableJavadocTokens();
118     }
119 
120     @Override
121     public void visitJavadocToken(DetailNode ast) {
122         if (isBlockDescription(ast) && !isInlineDescription(ast)) {
123             final List<DetailNode> textNodes = getAllNewlineNodes(ast);
124             for (DetailNode newlineNode : textNodes) {
125                 final DetailNode textNode = JavadocUtil.getNextSibling(newlineNode);
126                 if (textNode.getType() != JavadocTokenTypes.NEWLINE && isViolation(textNode)) {
127                     log(textNode.getLineNumber(), MSG_KEY, offset);
128                 }
129             }
130         }
131     }
132 
133     /**
134      * Checks if a text node meets the criteria for a violation.
135      * If the text is shorter than {@code offset} characters, then a violation is
136      * detected if the text is not blank or the next node is not a newline.
137      * If the text is longer than {@code offset} characters, then a violation is
138      * detected if any of the first {@code offset} characters are not blank.
139      *
140      * @param textNode the node to check.
141      * @return true if the node has a violation.
142      */
143     private boolean isViolation(DetailNode textNode) {
144         boolean result = false;
145         final String text = textNode.getText();
146         if (text.length() <= offset) {
147             if (CommonUtil.isBlank(text)) {
148                 final DetailNode nextNode = JavadocUtil.getNextSibling(textNode);
149                 if (nextNode != null && nextNode.getType() != JavadocTokenTypes.NEWLINE) {
150                     // text is blank but line hasn't ended yet
151                     result = true;
152                 }
153             }
154             else {
155                 // text is not blank
156                 result = true;
157             }
158         }
159         else if (!CommonUtil.isBlank(text.substring(1, offset + 1))) {
160             // first offset number of characters are not blank
161             result = true;
162         }
163         return result;
164     }
165 
166     /**
167      * Finds and collects all NEWLINE nodes inside DESCRIPTION node.
168      *
169      * @param descriptionNode DESCRIPTION node.
170      * @return List with NEWLINE nodes.
171      */
172     private static List<DetailNode> getAllNewlineNodes(DetailNode descriptionNode) {
173         final List<DetailNode> textNodes = new ArrayList<>();
174         DetailNode node = JavadocUtil.getFirstChild(descriptionNode);
175         while (JavadocUtil.getNextSibling(node) != null) {
176             if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) {
177                 final DetailNode descriptionNodeChild = JavadocUtil.getFirstChild(node);
178                 textNodes.addAll(getAllNewlineNodes(descriptionNodeChild));
179             }
180             else if (node.getType() == JavadocTokenTypes.HTML_ELEMENT_START
181                 || node.getType() == JavadocTokenTypes.ATTRIBUTE) {
182                 textNodes.addAll(getAllNewlineNodes(node));
183             }
184             if (node.getType() == JavadocTokenTypes.LEADING_ASTERISK) {
185                 textNodes.add(node);
186             }
187             node = JavadocUtil.getNextSibling(node);
188         }
189         return textNodes;
190     }
191 
192     /**
193      * Checks if the given description node is part of a block Javadoc tag.
194      *
195      * @param description the node to check
196      * @return {@code true} if the node is inside a block tag, {@code false} otherwise
197      */
198     private static boolean isBlockDescription(DetailNode description) {
199         boolean isBlock = false;
200         DetailNode currentNode = description;
201         while (currentNode != null) {
202             if (currentNode.getType() == JavadocTokenTypes.JAVADOC_TAG) {
203                 isBlock = true;
204                 break;
205             }
206             currentNode = currentNode.getParent();
207         }
208         return isBlock;
209     }
210 
211     /**
212      * Checks, if description node is a description of in-line tag.
213      *
214      * @param description DESCRIPTION node.
215      * @return true, if description node is a description of in-line tag.
216      */
217     private static boolean isInlineDescription(DetailNode description) {
218         boolean isInline = false;
219         DetailNode currentNode = description;
220         while (currentNode != null) {
221             if (currentNode.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
222                 isInline = true;
223                 break;
224             }
225             currentNode = currentNode.getParent();
226         }
227         return isInline;
228     }
229 
230 }