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   * <p>
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   * </p>
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   * <p>
54   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
55   * </p>
56   * <p>
57   * Violation Message Keys:
58   * </p>
59   * <ul>
60   * <li>
61   * {@code javadoc.missed.html.close}
62   * </li>
63   * <li>
64   * {@code javadoc.parse.rule.error}
65   * </li>
66   * <li>
67   * {@code javadoc.unclosedHtml}
68   * </li>
69   * <li>
70   * {@code javadoc.wrong.singleton.html.tag}
71   * </li>
72   * <li>
73   * {@code tag.continuation.indent}
74   * </li>
75   * </ul>
76   *
77   * @since 6.0
78   *
79   */
80  @StatelessCheck
81  public class JavadocTagContinuationIndentationCheck extends AbstractJavadocCheck {
82  
83      /**
84       * A key is pointing to the warning message text in "messages.properties"
85       * file.
86       */
87      public static final String MSG_KEY = "tag.continuation.indent";
88  
89      /** Default tag continuation indentation. */
90      private static final int DEFAULT_INDENTATION = 4;
91  
92      /**
93       * Specify how many spaces to use for new indentation level.
94       */
95      private int offset = DEFAULT_INDENTATION;
96  
97      /**
98       * Setter to specify how many spaces to use for new indentation level.
99       *
100      * @param offset custom value.
101      * @since 6.0
102      */
103     public void setOffset(int offset) {
104         this.offset = offset;
105     }
106 
107     @Override
108     public int[] getDefaultJavadocTokens() {
109         return new int[] {JavadocTokenTypes.DESCRIPTION };
110     }
111 
112     @Override
113     public int[] getRequiredJavadocTokens() {
114         return getAcceptableJavadocTokens();
115     }
116 
117     @Override
118     public void visitJavadocToken(DetailNode ast) {
119         if (!isInlineDescription(ast)) {
120             final List<DetailNode> textNodes = getAllNewlineNodes(ast);
121             for (DetailNode newlineNode : textNodes) {
122                 final DetailNode textNode = JavadocUtil.getNextSibling(newlineNode);
123                 if (textNode.getType() == JavadocTokenTypes.TEXT && isViolation(textNode)) {
124                     log(textNode.getLineNumber(), MSG_KEY, offset);
125                 }
126             }
127         }
128     }
129 
130     /**
131      * Checks if a text node meets the criteria for a violation.
132      * If the text is shorter than {@code offset} characters, then a violation is
133      * detected if the text is not blank or the next node is not a newline.
134      * If the text is longer than {@code offset} characters, then a violation is
135      * detected if any of the first {@code offset} characters are not blank.
136      *
137      * @param textNode the node to check.
138      * @return true if the node has a violation.
139      */
140     private boolean isViolation(DetailNode textNode) {
141         boolean result = false;
142         final String text = textNode.getText();
143         if (text.length() <= offset) {
144             if (CommonUtil.isBlank(text)) {
145                 final DetailNode nextNode = JavadocUtil.getNextSibling(textNode);
146                 if (nextNode != null && nextNode.getType() != JavadocTokenTypes.NEWLINE) {
147                     // text is blank but line hasn't ended yet
148                     result = true;
149                 }
150             }
151             else {
152                 // text is not blank
153                 result = true;
154             }
155         }
156         else if (!CommonUtil.isBlank(text.substring(1, offset + 1))) {
157             // first offset number of characters are not blank
158             result = true;
159         }
160         return result;
161     }
162 
163     /**
164      * Finds and collects all NEWLINE nodes inside DESCRIPTION node.
165      *
166      * @param descriptionNode DESCRIPTION node.
167      * @return List with NEWLINE nodes.
168      */
169     private static List<DetailNode> getAllNewlineNodes(DetailNode descriptionNode) {
170         final List<DetailNode> textNodes = new ArrayList<>();
171         DetailNode node = JavadocUtil.getFirstChild(descriptionNode);
172         while (JavadocUtil.getNextSibling(node) != null) {
173             if (node.getType() == JavadocTokenTypes.HTML_ELEMENT) {
174                 final DetailNode descriptionNodeChild = JavadocUtil.getFirstChild(node);
175                 textNodes.addAll(getAllNewlineNodes(descriptionNodeChild));
176             }
177             if (node.getType() == JavadocTokenTypes.LEADING_ASTERISK) {
178                 textNodes.add(node);
179             }
180             node = JavadocUtil.getNextSibling(node);
181         }
182         return textNodes;
183     }
184 
185     /**
186      * Checks, if description node is a description of in-line tag.
187      *
188      * @param description DESCRIPTION node.
189      * @return true, if description node is a description of in-line tag.
190      */
191     private static boolean isInlineDescription(DetailNode description) {
192         boolean isInline = false;
193         DetailNode currentNode = description;
194         while (currentNode != null) {
195             if (currentNode.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
196                 isInline = true;
197                 break;
198             }
199             currentNode = currentNode.getParent();
200         }
201         return isInline;
202     }
203 
204 }