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.List;
23  
24  import javax.annotation.Nullable;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailNode;
28  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
29  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
30  
31  /**
32   * <div>
33   * Checks that Javadoc inline tags are preferred over escaping entities.
34   * According to <a href="https://cr.openjdk.org/~alundblad/styleguide/index-v6.html">
35   * OpenJDK Style Guidelines v6</a>
36   * Javadoc inline tags should be preferred over their HTML equivalents.
37   * Entities that are flagged by the check are:
38   * <ul>
39   * <li>{@literal &lt;}</li>
40   * <li>{@literal &gt;}</li>
41   * <li>{@literal &amp;}</li>
42   * <li>{@literal &quot;}</li>
43   * <li>{@literal &apos;}</li>
44   * </ul>
45   *
46   * <p>
47   * Reason of only these entities are flagged is given here :
48   * <a href="https://www.w3.org/TR/WD-xml-970807#dt-escape">Predefined Entities</a>
49   * </p>
50   *
51   * </div>
52   *
53   * <p>
54   * <b>Not flagged:</b>
55   * </p>
56   * <ul>
57   * <li>Content inside {@code <pre>} and {@code <code>} blocks (code examples)</li>
58   * <li>Content inside {@code {@code}}, {@code {@literal}}, {@code {@snippet}} inline tags</li>
59   * </ul>
60   *
61   * @since 13.11.0
62   */
63  @StatelessCheck
64  public class PreferLiteralJavadocInlineTagCheck extends AbstractJavadocCheck {
65  
66      /**
67       * A key is pointing to the warning message text in "messages.properties" file.
68       */
69      public static final String MSG_KEY = "prefer.literal.javadoc.inline.tag";
70  
71      /** HTML entities to be replaced with {@code {@literal ...}}. */
72      private static final List<String> HTML_ENTITIES =
73              List.of("&lt;", "&gt;", "&amp;", "&quot;", "&apos;");
74  
75      /**
76       * Creates a new {@code PreferJavadocInlineTagsCheck} instance.
77       */
78      public PreferLiteralJavadocInlineTagCheck() {
79          // no code by default
80      }
81  
82      @Override
83      public int[] getDefaultJavadocTokens() {
84          return new int[] {
85              JavadocCommentsTokenTypes.TEXT,
86          };
87      }
88  
89      @Override
90      public int[] getRequiredJavadocTokens() {
91          return getAcceptableJavadocTokens();
92      }
93  
94      @Override
95      public void visitJavadocToken(DetailNode ast) {
96          if (!isInsidePreOrCodeTag(ast) && !isInsideInlineTag(ast)) {
97              checkForHtmlEntities(ast);
98          }
99      }
100 
101     /**
102      * Checks Javadoc TEXT nodes for HTML entities that should be replaced with
103      * {@code {@literal ...}}.
104      *
105      * @param textNode the TEXT node to check
106      */
107     private void checkForHtmlEntities(DetailNode textNode) {
108         final String text = textNode.getText();
109 
110         for (String htmlEntity : HTML_ENTITIES) {
111             int htmlEntityIndex = text.indexOf(htmlEntity);
112             while (htmlEntityIndex >= 0) {
113                 log(textNode.getLineNumber(), textNode.getColumnNumber() + htmlEntityIndex,
114                     MSG_KEY, htmlEntity);
115                 htmlEntityIndex = text.indexOf(htmlEntity, htmlEntityIndex + htmlEntity.length());
116             }
117         }
118     }
119 
120     /**
121      * Gets the tag name from an HTML_ELEMENT node.
122      *
123      * @param htmlElement the HTML_ELEMENT node
124      * @return the tag name (e.g., "code", "a")
125      */
126     @Nullable
127     private static String getHtmlTagName(DetailNode htmlElement) {
128         String result = null;
129         final DetailNode htmlTagStart = JavadocUtil.findFirstToken(
130             htmlElement, JavadocCommentsTokenTypes.HTML_TAG_START);
131         if (htmlTagStart != null) {
132             final DetailNode tagName = JavadocUtil.findFirstToken(
133                 htmlTagStart, JavadocCommentsTokenTypes.TAG_NAME);
134             result = tagName.getText();
135         }
136         return result;
137     }
138 
139     /**
140      * Checks if the node is inside a pre or code tag.
141      *
142      * @param node the node to check
143      * @return true if inside a pre or code block
144      */
145     private static boolean isInsidePreOrCodeTag(DetailNode node) {
146         DetailNode current = node;
147         boolean insidePreOrCode = false;
148         while (current != null) {
149             final String tagName = getHtmlTagName(current);
150             if ("pre".equalsIgnoreCase(tagName) || "code".equalsIgnoreCase(tagName)) {
151                 insidePreOrCode = true;
152                 break;
153             }
154             current = current.getParent();
155         }
156         return insidePreOrCode;
157     }
158 
159     /**
160      * Checks if the node is inside a {@code {@code}}, {@code {@literal}} or {@code {@snippet}}
161      * inline tag.
162      * Content inside these tags is meant to be displayed literally, so HTML patterns
163      * within them are intentional examples and should not be flagged.
164      *
165      * @param node the node to check
166      * @return true if inside a code, literal or snippet inline tag
167      */
168     private static boolean isInsideInlineTag(DetailNode node) {
169         return node.getParent().getType() == JavadocCommentsTokenTypes.CODE_INLINE_TAG
170                     || node.getParent().getType() == JavadocCommentsTokenTypes.LITERAL_INLINE_TAG
171                     || node.getParent().getType() == JavadocCommentsTokenTypes.SNIPPET_BODY;
172     }
173 
174 }