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.Deque;
24  import java.util.regex.Pattern;
25  
26  import javax.annotation.Nullable;
27  
28  import com.puppycrawl.tools.checkstyle.StatelessCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.DetailNode;
31  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
32  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
34  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
35  
36  /**
37   * <div>
38   * Detects a user-defined Javadoc block tag and reports a violation when the tag
39   * is present with text that does not match {@code tagTextPattern}.
40   * With the default pattern {@code ^$} (same as {@code Regexp} format default;
41   * matches only empty content), any non-empty text of the configured tag is a
42   * violation. No violation is reported when there is no Javadoc or when
43   * {@code tag} is not configured.
44   * </div>
45   *
46   * @since 13.10.0
47   */
48  @StatelessCheck
49  public class IllegalBlockTagCheck extends AbstractJavadocCheck {
50  
51      /**
52       * A key is pointing to the warning message text in "messages.properties"
53       * file.
54       */
55      public static final String MSG_ILLEGAL_PATTERN = "illegalblocktag.illegalPattern";
56  
57      /** Pattern that matches only empty content (Regexp format default). */
58      private static final Pattern MATCH_NOTHING = CommonUtil.createPattern("^$");
59  
60      /** Specify the regexp that tag content is allowed to match. */
61      private Pattern tagTextPattern = MATCH_NOTHING;
62  
63      /** Specify the name of tag. */
64      @Nullable
65      private String tag;
66  
67      /**
68       * Creates a new {@code IllegalBlockTagCheck} instance.
69       */
70      public IllegalBlockTagCheck() {
71          // no code by default
72      }
73  
74      /**
75       * Setter to specify the name of tag.
76       *
77       * @param tag tag to check
78       * @since 13.10.0
79       */
80      public void setTag(String tag) {
81          this.tag = tag;
82      }
83  
84      /**
85       * Setter to specify the regexp that tag content is allowed to match.
86       * Content that does not match is treated as illegal.
87       *
88       * @param pattern a {@code Pattern} value
89       * @since 13.10.0
90       */
91      public void setTagTextPattern(Pattern pattern) {
92          tagTextPattern = pattern;
93      }
94  
95      /**
96       * Setter to control when to print violations if the Javadoc being examined by this check
97       * violates the tight html rules defined at
98       * <a href="https://checkstyle.org/writing-javadoc-checks.html#Tight-HTML_rules">
99       *     Tight-HTML Rules</a>.
100      *
101      * @param shouldReportViolation value to which the field shall be set to
102      * @since 13.10.0
103      * @propertySince 13.10.0
104      */
105     @Override
106     public void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) {
107         super.setViolateExecutionOnNonTightHtml(shouldReportViolation);
108     }
109 
110     @Override
111     public int[] getRequiredTokens() {
112         return CommonUtil.EMPTY_INT_ARRAY;
113     }
114 
115     @Override
116     public int[] getDefaultTokens() {
117         return getAcceptableTokens();
118     }
119 
120     @Override
121     public int[] getAcceptableTokens() {
122         return new int[] {
123             TokenTypes.INTERFACE_DEF,
124             TokenTypes.CLASS_DEF,
125             TokenTypes.ENUM_DEF,
126             TokenTypes.ANNOTATION_DEF,
127             TokenTypes.METHOD_DEF,
128             TokenTypes.CTOR_DEF,
129             TokenTypes.ENUM_CONSTANT_DEF,
130             TokenTypes.ANNOTATION_FIELD_DEF,
131             TokenTypes.RECORD_DEF,
132             TokenTypes.COMPACT_CTOR_DEF,
133         };
134     }
135 
136     @Override
137     public int[] getDefaultJavadocTokens() {
138         return new int[] {
139             JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG,
140         };
141     }
142 
143     @Override
144     public int[] getRequiredJavadocTokens() {
145         return getAcceptableJavadocTokens();
146     }
147 
148     @Override
149     public void visitToken(DetailAST ast) {
150         final DetailAST javadocComment = findJavadoc(ast);
151         if (javadocComment != null) {
152             super.visitToken(javadocComment);
153         }
154     }
155 
156     @Override
157     public void visitJavadocToken(DetailNode ast) {
158         final String tagName = "@" + JavadocUtil.getTagName(ast);
159         if (tagName.equals(tag)) {
160             final String content = getTagContent(ast);
161             if (!tagTextPattern.matcher(content).find()) {
162                 log(ast, MSG_ILLEGAL_PATTERN, JavadocUtil.getTagName(ast));
163             }
164         }
165     }
166 
167     /**
168      * Returns the raw content of the tag.
169      *
170      * @param javadocBlockTagNode The node representing a Javadoc block tag.
171      *       This node must be of type {@link JavadocCommentsTokenTypes#JAVADOC_BLOCK_TAG}
172      * @return The raw content of the tag.
173      */
174     private static String getTagContent(DetailNode javadocBlockTagNode) {
175         final DetailNode tagNodeNextSibling = JavadocUtil.findFirstToken(
176             javadocBlockTagNode.getFirstChild(),
177             JavadocCommentsTokenTypes.TAG_NAME).getNextSibling();
178 
179         final int stringBuilderCapacity = 128;
180         final StringBuilder rawTextBuilder = new StringBuilder(stringBuilderCapacity);
181         if (tagNodeNextSibling != null) {
182             // DFS to extract texts of all leaf nodes
183             final Deque<DetailNode> stack = new ArrayDeque<>();
184             stack.push(tagNodeNextSibling);
185 
186             while (!stack.isEmpty()) {
187                 final DetailNode currentNode = stack.pop();
188 
189                 // append text if node is a leaf
190                 if (currentNode.getFirstChild() == null) {
191                     rawTextBuilder.append(currentNode.getText());
192                 }
193 
194                 final DetailNode nextSibling = currentNode.getNextSibling();
195                 final DetailNode firstChild = currentNode.getFirstChild();
196 
197                 if (nextSibling != null) {
198                     stack.push(nextSibling);
199                 }
200                 if (firstChild != null) {
201                     stack.push(firstChild);
202                 }
203             }
204         }
205 
206         return rawTextBuilder.toString().stripLeading();
207     }
208 
209     /**
210      * Finds the Javadoc comment associated with a structural AST node.
211      *
212      * @param ast the structural node (e.g., CLASS_DEF, METHOD_DEF)
213      * @return the Javadoc block comment if found, or null
214      */
215     @Nullable
216     private static DetailAST findJavadoc(DetailAST ast) {
217         DetailAST cmt = ast.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
218         if (cmt == null) {
219             final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
220             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
221 
222             if (modifiers != null) {
223                 final DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
224                 cmt = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
225                 if (annotation != null) {
226                     cmt = annotation.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
227                 }
228             }
229             if (cmt == null && type != null) {
230                 cmt = type.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
231             }
232         }
233         return cmt;
234     }
235 
236 }