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 <}</li>
40 * <li>{@literal >}</li>
41 * <li>{@literal &}</li>
42 * <li>{@literal "}</li>
43 * <li>{@literal '}</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("<", ">", "&", """, "'");
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 }