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.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.DESCRIPTION };
112     }
113 
114     @Override
115     public int[] getRequiredJavadocTokens() {
116         return getAcceptableJavadocTokens();
117     }
118 
119     @Override
120     public void visitJavadocToken(DetailNode ast) {
121         if (!isInlineDescription(ast)) {
122             final List<DetailNode> textNodes = getAllNewlineNodes(ast);
123             for (DetailNode newlineNode : textNodes) {
124                 final DetailNode textNode = JavadocUtil.getNextSibling(newlineNode);
125                 if (textNode.getType() == JavadocTokenTypes.TEXT && isViolation(textNode)) {
126                     log(textNode.getLineNumber(), MSG_KEY, offset);
127                 }
128             }
129         }
130     }
131 
132     /**
133      * Checks if a text node meets the criteria for a violation.
134      * If the text is shorter than {@code offset} characters, then a violation is
135      * detected if the text is not blank or the next node is not a newline.
136      * If the text is longer than {@code offset} characters, then a violation is
137      * detected if any of the first {@code offset} characters are not blank.
138      *
139      * @param textNode the node to check.
140      * @return true if the node has a violation.
141      */
142     private boolean isViolation(DetailNode textNode) {
143         boolean result = false;
144         final String text = textNode.getText();
145         if (text.length() <= offset) {
146             if (CommonUtil.isBlank(text)) {
147                 final DetailNode nextNode = JavadocUtil.getNextSibling(textNode);
148                 if (nextNode != null && nextNode.getType() != JavadocTokenTypes.NEWLINE) {
149                     // text is blank but line hasn't ended yet
150                     result = true;
151                 }
152             }
153             else {
154                 // text is not blank
155                 result = true;
156             }
157         }
158         else if (!CommonUtil.isBlank(text.substring(1, offset + 1))) {
159             // first offset number of characters are not blank
160             result = true;
161         }
162         return result;
163     }
164 
165     /**
166      * Finds and collects all NEWLINE nodes inside DESCRIPTION node.
167      *
168      * @param descriptionNode DESCRIPTION node.
169      * @return List with NEWLINE nodes.
170      */
171     private static List<DetailNode> getAllNewlineNodes(DetailNode descriptionNode) {
172         final List<DetailNode> textNodes = new ArrayList<>();
173         DetailNode node = JavadocUtil.getFirstChild(descriptionNode);
174         while (JavadocUtil.getNextSibling(node) != null) {
175             if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) {
176                 final DetailNode descriptionNodeChild = JavadocUtil.getFirstChild(node);
177                 textNodes.addAll(getAllNewlineNodes(descriptionNodeChild));
178             }
179             if (node.getType() == JavadocTokenTypes.LEADING_ASTERISK) {
180                 textNodes.add(node);
181             }
182             node = JavadocUtil.getNextSibling(node);
183         }
184         return textNodes;
185     }
186 
187     /**
188      * Checks, if description node is a description of in-line tag.
189      *
190      * @param description DESCRIPTION node.
191      * @return true, if description node is a description of in-line tag.
192      */
193     private static boolean isInlineDescription(DetailNode description) {
194         boolean isInline = false;
195         DetailNode currentNode = description;
196         while (currentNode != null) {
197             if (currentNode.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
198                 isInline = true;
199                 break;
200             }
201             currentNode = currentNode.getParent();
202         }
203         return isInline;
204     }
205 
206 }