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 com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.DetailNode;
24  import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
25  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
26  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
27  
28  /**
29   * <p>
30   * Checks the Javadoc paragraph.
31   * </p>
32   * <p>
33   * Checks that:
34   * </p>
35   * <ul>
36   * <li>There is one blank line between each of two paragraphs.</li>
37   * <li>Each paragraph but the first has &lt;p&gt; immediately
38   * before the first word, with no space after.</li>
39   * </ul>
40   * <ul>
41   * <li>
42   * Property {@code allowNewlineParagraph} - Control whether the &lt;p&gt; tag
43   * should be placed immediately before the first word.
44   * Type is {@code boolean}.
45   * Default value is {@code true}.
46   * </li>
47   * <li>
48   * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
49   * if the Javadoc being examined by this check violates the tight html rules defined at
50   * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
51   * Tight-HTML Rules</a>.
52   * Type is {@code boolean}.
53   * Default value is {@code false}.
54   * </li>
55   * </ul>
56   * <p>
57   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
58   * </p>
59   * <p>
60   * Violation Message Keys:
61   * </p>
62   * <ul>
63   * <li>
64   * {@code javadoc.missed.html.close}
65   * </li>
66   * <li>
67   * {@code javadoc.paragraph.line.before}
68   * </li>
69   * <li>
70   * {@code javadoc.paragraph.misplaced.tag}
71   * </li>
72   * <li>
73   * {@code javadoc.paragraph.redundant.paragraph}
74   * </li>
75   * <li>
76   * {@code javadoc.paragraph.tag.after}
77   * </li>
78   * <li>
79   * {@code javadoc.parse.rule.error}
80   * </li>
81   * <li>
82   * {@code javadoc.unclosedHtml}
83   * </li>
84   * <li>
85   * {@code javadoc.wrong.singleton.html.tag}
86   * </li>
87   * </ul>
88   *
89   * @since 6.0
90   */
91  @StatelessCheck
92  public class JavadocParagraphCheck extends AbstractJavadocCheck {
93  
94      /**
95       * A key is pointing to the warning message text in "messages.properties"
96       * file.
97       */
98      public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after";
99  
100     /**
101      * A key is pointing to the warning message text in "messages.properties"
102      * file.
103      */
104     public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before";
105 
106     /**
107      * A key is pointing to the warning message text in "messages.properties"
108      * file.
109      */
110     public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph";
111 
112     /**
113      * A key is pointing to the warning message text in "messages.properties"
114      * file.
115      */
116     public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag";
117 
118     /**
119      * Control whether the &lt;p&gt; tag should be placed immediately before the first word.
120      */
121     private boolean allowNewlineParagraph = true;
122 
123     /**
124      * Setter to control whether the &lt;p&gt; tag should be placed
125      * immediately before the first word.
126      *
127      * @param value value to set.
128      * @since 6.9
129      */
130     public void setAllowNewlineParagraph(boolean value) {
131         allowNewlineParagraph = value;
132     }
133 
134     @Override
135     public int[] getDefaultJavadocTokens() {
136         return new int[] {
137             JavadocTokenTypes.NEWLINE,
138             JavadocTokenTypes.HTML_ELEMENT,
139         };
140     }
141 
142     @Override
143     public int[] getRequiredJavadocTokens() {
144         return getAcceptableJavadocTokens();
145     }
146 
147     @Override
148     public void visitJavadocToken(DetailNode ast) {
149         if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
150             checkEmptyLine(ast);
151         }
152         else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
153                 && JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) {
154             checkParagraphTag(ast);
155         }
156     }
157 
158     /**
159      * Determines whether or not the next line after empty line has paragraph tag in the beginning.
160      *
161      * @param newline NEWLINE node.
162      */
163     private void checkEmptyLine(DetailNode newline) {
164         final DetailNode nearestToken = getNearestNode(newline);
165         if (nearestToken.getType() == JavadocTokenTypes.TEXT
166                 && !CommonUtil.isBlank(nearestToken.getText())) {
167             log(newline.getLineNumber(), MSG_TAG_AFTER);
168         }
169     }
170 
171     /**
172      * Determines whether or not the line with paragraph tag has previous empty line.
173      *
174      * @param tag html tag.
175      */
176     private void checkParagraphTag(DetailNode tag) {
177         final DetailNode newLine = getNearestEmptyLine(tag);
178         if (isFirstParagraph(tag)) {
179             log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH);
180         }
181         else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
182             log(tag.getLineNumber(), MSG_LINE_BEFORE);
183         }
184         if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) {
185             log(tag.getLineNumber(), MSG_MISPLACED_TAG);
186         }
187     }
188 
189     /**
190      * Returns nearest node.
191      *
192      * @param node DetailNode node.
193      * @return nearest node.
194      */
195     private static DetailNode getNearestNode(DetailNode node) {
196         DetailNode currentNode = node;
197         while (currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK
198                 || currentNode.getType() == JavadocTokenTypes.NEWLINE) {
199             currentNode = JavadocUtil.getNextSibling(currentNode);
200         }
201         return currentNode;
202     }
203 
204     /**
205      * Determines whether or not the line is empty line.
206      *
207      * @param newLine NEWLINE node.
208      * @return true, if line is empty line.
209      */
210     private static boolean isEmptyLine(DetailNode newLine) {
211         boolean result = false;
212         DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
213         if (previousSibling != null
214                 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
215             if (previousSibling.getType() == JavadocTokenTypes.TEXT
216                     && CommonUtil.isBlank(previousSibling.getText())) {
217                 previousSibling = JavadocUtil.getPreviousSibling(previousSibling);
218             }
219             result = previousSibling != null
220                     && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
221         }
222         return result;
223     }
224 
225     /**
226      * Determines whether or not the line with paragraph tag is first line in javadoc.
227      *
228      * @param paragraphTag paragraph tag.
229      * @return true, if line with paragraph tag is first line in javadoc.
230      */
231     private static boolean isFirstParagraph(DetailNode paragraphTag) {
232         boolean result = true;
233         DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag);
234         while (previousNode != null) {
235             if (previousNode.getType() == JavadocTokenTypes.TEXT
236                     && !CommonUtil.isBlank(previousNode.getText())
237                 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
238                     && previousNode.getType() != JavadocTokenTypes.NEWLINE
239                     && previousNode.getType() != JavadocTokenTypes.TEXT) {
240                 result = false;
241                 break;
242             }
243             previousNode = JavadocUtil.getPreviousSibling(previousNode);
244         }
245         return result;
246     }
247 
248     /**
249      * Finds and returns nearest empty line in javadoc.
250      *
251      * @param node DetailNode node.
252      * @return Some nearest empty line in javadoc.
253      */
254     private static DetailNode getNearestEmptyLine(DetailNode node) {
255         DetailNode newLine = node;
256         while (newLine != null) {
257             final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
258             if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
259                 break;
260             }
261             newLine = previousSibling;
262         }
263         return newLine;
264     }
265 
266     /**
267      * Tests whether the paragraph tag is immediately followed by the text.
268      *
269      * @param tag html tag.
270      * @return true, if the paragraph tag is immediately followed by the text.
271      */
272     private static boolean isImmediatelyFollowedByText(DetailNode tag) {
273         final DetailNode nextSibling = JavadocUtil.getNextSibling(tag);
274         return nextSibling.getType() == JavadocTokenTypes.NEWLINE
275                 || nextSibling.getType() == JavadocTokenTypes.EOF
276                 || nextSibling.getText().startsWith(" ");
277     }
278 
279 }