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