View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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.ArrayDeque;
23  import java.util.ArrayList;
24  import java.util.Deque;
25  import java.util.List;
26  
27  import javax.annotation.Nullable;
28  
29  import com.puppycrawl.tools.checkstyle.StatelessCheck;
30  import com.puppycrawl.tools.checkstyle.api.DetailNode;
31  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
32  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
33  
34  /**
35   * <div>
36   * Checks that Javadoc inline tags {@code {@code ...}} and {@code {@snippet ...}}
37   * are preferred over HTML tags {@code <code>} and {@code <pre>}.
38   * </div>
39   *
40   * <p>
41   * This check enforces using either {@code {@code ...}} or {@code {@snippet ...}} inline tags
42   * instead of single-line {@code <code>} and {@code <pre>} HTML tags, and using
43   * {@code {@snippet ...}} inline tags instead of multi-line {@code <code>} and {@code <pre>}
44   * HTML tags.
45   * </p>
46   *
47   * <p>
48   * Per <a href="https://cr.openjdk.org/~alundblad/styleguide/index-v6.html">
49   * OpenJDK Style Guidelines v6</a>,
50   * Javadoc inline tags should be preferred over their HTML equivalents.
51   * </p>
52   *
53   * <p>
54   * To suppress violation for snippet inline tag:
55   * </p>
56   *
57   * <div class="wrapper"><pre class="prettyprint"><code class="language-xml">
58   *     &lt;module name="SuppressionSingleFilter"&gt;
59   *       &lt;property name="checks" value="PreferCodeOrSnippetJavadocInlineTag"/&gt;
60   *       &lt;property name="files" value="file-name"/&gt;
61   *       &lt;property name="message" value="Use snippet inline tag instead of.*"/&gt;
62   *     &lt;/module&gt;
63   * </code></pre></div>
64   *
65   * <b>Not Flagged :</b>
66   * <ul>
67   *     <li>Tags which have unbalanced curly braces</li>
68   *     <li>Tags which have content that starts with star.</li>
69   *     <li>Tags which are inside other tags</li>
70   * </ul>
71   *
72   * @since 14.1.0
73   */
74  @StatelessCheck
75  public class PreferCodeOrSnippetJavadocInlineTagCheck extends AbstractJavadocCheck {
76  
77      /**
78       * A key is pointing to the warning message text in "messages.properties"
79       * file.
80       */
81      public static final String MSG_KEY_SINGLE_LINE = "prefer.code.javadoc.singleline.tag";
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_MULTI_LINE = "prefer.code.javadoc.multiline.tag";
88  
89      /**
90       * Creates a new {@code PreferCodeOrSnippetJavadocInlineTagCheck} instance.
91       */
92      public PreferCodeOrSnippetJavadocInlineTagCheck() {
93          // no code by default
94      }
95  
96      @Override
97      public int[] getRequiredJavadocTokens() {
98          return new int[] {
99              JavadocCommentsTokenTypes.HTML_ELEMENT,
100         };
101     }
102 
103     @Override
104     public int[] getDefaultJavadocTokens() {
105         return getRequiredJavadocTokens();
106     }
107 
108     @Override
109     public void visitJavadocToken(DetailNode node) {
110         if (isCodeOrPreTag(node) && isCompleteTag(node) && !isNested(node)) {
111             final List<DetailNode> textNodes = collectTextNodes(node);
112 
113             if (isSingleLineTag(node) && containsBalancedBraces(textNodes)) {
114                 log(node, MSG_KEY_SINGLE_LINE, getHtmlTagName(node));
115             }
116             else if (isConvertableInInlineTag(textNodes)) {
117                 log(node, MSG_KEY_MULTI_LINE, getHtmlTagName(node));
118             }
119         }
120     }
121 
122     /**
123      * Checks if the tag is code or pre tag.
124      *
125      * @param node the node to check
126      * @return {@code true} if the tag is code or pre tag, {@code false} otherwise
127      */
128     private static boolean isCodeOrPreTag(DetailNode node) {
129         final String tagName = getHtmlTagName(node);
130         return "code".equals(tagName) || "pre".equals(tagName);
131     }
132 
133     /**
134      * Checks if the tag is nested inside any other code or pre tag.
135      *
136      * @param node the node to check
137      * @return {@code true} if the tag is nested, {@code false} otherwise
138      */
139     private static boolean isNested(DetailNode node) {
140         boolean result = false;
141         DetailNode parent = node.getParent();
142         while (parent != null) {
143             if (isCodeOrPreTag(parent)) {
144                 result = true;
145                 break;
146             }
147             parent = parent.getParent();
148         }
149         return result;
150     }
151 
152     /**
153      * Checks if the tag is convertable in inline tag.
154      *
155      * @param listOfTextNodes the list of text nodes to check.
156      * @return {@code true} if the tag is convertable in inline tag, {@code false} otherwise.
157      */
158     public static boolean isConvertableInInlineTag(Iterable<DetailNode> listOfTextNodes) {
159         return containsBalancedBraces(listOfTextNodes) && !isStartWithStar(listOfTextNodes);
160     }
161 
162     /**
163      * Checks if the text list contains balanced braces.
164      *
165      * @param listOfTextNodes the list of text nodes to check
166      * @return {@code true} if the text list contains balanced braces, {@code false} otherwise
167      */
168     public static boolean containsBalancedBraces(Iterable<DetailNode> listOfTextNodes) {
169         int braceCount = 0;
170         for (DetailNode node: listOfTextNodes) {
171             final String text = node.getText();
172             for (int idx = 0; idx < text.length(); idx++) {
173                 final char letter = text.charAt(idx);
174                 if (letter == '{') {
175                     braceCount++;
176                 }
177                 else if (letter == '}') {
178                     braceCount--;
179                 }
180                 if (braceCount < 0) {
181                     break;
182                 }
183             }
184         }
185         return braceCount == 0;
186     }
187 
188     /**
189      * Checks if the first element of the text list starts with a star.
190      *
191      * @param listOfTextNodes the list of text nodes to check
192      * @return {@code true} if the first element of the text list does not start
193      *         with a star, {@code false} otherwise
194      */
195     private static boolean isStartWithStar(Iterable<DetailNode> listOfTextNodes) {
196         boolean result = false;
197         for (DetailNode node : listOfTextNodes) {
198             final String text = node.getText().trim();
199             if (text.startsWith("*")) {
200                 result = true;
201                 break;
202             }
203         }
204         return result;
205     }
206 
207     /**
208      * Collects all text nodes contained within the specified node, including
209      * text nested inside HTML elements and inline tags.
210      *
211      * @param node the root {@code DetailNode} to extract text nodes from
212      * @return a list of text {@code DetailNode} instances
213      */
214     public static List<DetailNode> collectTextNodes(DetailNode node) {
215         final DetailNode rootNode = JavadocUtil.findFirstToken(node,
216                 JavadocCommentsTokenTypes.HTML_CONTENT);
217         final List<DetailNode> textNodes = new ArrayList<>();
218         final List<DetailNode> inlineTags = new ArrayList<>();
219         final Deque<DetailNode> htmlElements = new ArrayDeque<>();
220 
221         if (rootNode != null) {
222             textNodes.addAll(JavadocUtil.getAllNodesOfType(rootNode,
223                 JavadocCommentsTokenTypes.TEXT));
224             inlineTags.addAll(JavadocUtil.getAllNodesOfType(
225                 rootNode, JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG));
226             htmlElements.addAll(JavadocUtil.getAllNodesOfType(
227                 rootNode, JavadocCommentsTokenTypes.HTML_ELEMENT));
228         }
229 
230         while (!htmlElements.isEmpty()) {
231             final DetailNode currentHtmlElement = htmlElements.pop();
232             final DetailNode currentHtmlContent = JavadocUtil.findFirstToken(currentHtmlElement,
233                     JavadocCommentsTokenTypes.HTML_CONTENT);
234 
235             if (currentHtmlContent != null) {
236                 textNodes.addAll(JavadocUtil.getAllNodesOfType(
237                     currentHtmlContent, JavadocCommentsTokenTypes.TEXT));
238                 inlineTags.addAll(JavadocUtil.getAllNodesOfType(
239                     currentHtmlContent, JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG));
240                 htmlElements.addAll(JavadocUtil.getAllNodesOfType(
241                     currentHtmlContent, JavadocCommentsTokenTypes.HTML_ELEMENT));
242             }
243         }
244 
245         textNodes.addAll(getTextNodesFromInlineTags(inlineTags));
246         return textNodes;
247     }
248 
249     /**
250      * Extracts text nodes from child nodes of the provided list of inline tags.
251      *
252      * @param inlineTags the list of inline tag {@code DetailNode}s
253      * @return a list of text {@code DetailNode} instances found inside the inline tags
254      */
255     private static List<DetailNode> getTextNodesFromInlineTags(Iterable<DetailNode> inlineTags) {
256         final List<DetailNode> textNodes = new ArrayList<>();
257         for (DetailNode inlineTag : inlineTags) {
258             textNodes.addAll(JavadocUtil.getAllNodesOfType(
259                 inlineTag.getFirstChild(), JavadocCommentsTokenTypes.TEXT));
260         }
261         return textNodes;
262     }
263 
264     /**
265      * Checks if the tag is single-line.
266      *
267      * @param node the node to check
268      * @return {@code true} if the tag is single-line, {@code false} otherwise
269      */
270     private static boolean isSingleLineTag(DetailNode node) {
271         final DetailNode endOfTag =
272             JavadocUtil.findFirstToken(node, JavadocCommentsTokenTypes.HTML_TAG_END);
273         return node.getLineNumber() == endOfTag.getLineNumber();
274     }
275 
276     /**
277      * Checks if the tag is complete.
278      *
279      * @param node the node to check
280      * @return {@code true} if the tag is complete, {@code false} otherwise
281      */
282     private static boolean isCompleteTag(DetailNode node) {
283         final DetailNode endOfTag =
284             JavadocUtil.findFirstToken(node, JavadocCommentsTokenTypes.HTML_TAG_END);
285         return endOfTag != null;
286     }
287 
288     /**
289      * Gets the tag name from an HTML_ELEMENT node.
290      *
291      * @param htmlElement the HTML_ELEMENT node
292      * @return the tag name (e.g., "code", "pre")
293      */
294     @Nullable
295     private static String getHtmlTagName(DetailNode htmlElement) {
296         String result = null;
297         final DetailNode htmlTagStart = JavadocUtil.findFirstToken(
298             htmlElement, JavadocCommentsTokenTypes.HTML_TAG_START);
299         if (htmlTagStart != null) {
300             final DetailNode tagName = JavadocUtil.findFirstToken(
301                 htmlTagStart, JavadocCommentsTokenTypes.TAG_NAME);
302             result = tagName.getText();
303         }
304         return result;
305     }
306 
307 }