View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  import javax.annotation.Nullable;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34  
35  /**
36   * <div>
37   * Requires user defined Javadoc tag to be present in Javadoc comment with defined format.
38   * To define the format for a tag, set property tagFormat to a regular expression.
39   * Property tagSeverity is used for severity of events when the tag exists.
40   * No violation reported in case there is no javadoc.
41   * </div>
42   *
43   * @since 4.2
44   */
45  @StatelessCheck
46  public class WriteTagCheck
47      extends AbstractCheck {
48  
49      /**
50       * A key is pointing to the warning message text in "messages.properties"
51       * file.
52       */
53      public static final String MSG_MISSING_TAG = "type.missingTag";
54  
55      /**
56       * A key is pointing to the warning message text in "messages.properties"
57       * file.
58       */
59      public static final String MSG_WRITE_TAG = "javadoc.writeTag";
60  
61      /**
62       * A key is pointing to the warning message text in "messages.properties"
63       * file.
64       */
65      public static final String MSG_TAG_FORMAT = "type.tagFormat";
66  
67      /** Line split pattern. */
68      private static final Pattern LINE_SPLIT_PATTERN = Pattern.compile("\\R");
69  
70      /** Compiled regexp to match tag. */
71      private Pattern tagRegExp;
72      /** Specify the regexp to match tag content. */
73      private Pattern tagFormat;
74  
75      /** Specify the name of tag. */
76      private String tag;
77      /** Specify the severity level when tag is found and printed. */
78      private SeverityLevel tagSeverity = SeverityLevel.INFO;
79  
80      /**
81       * Setter to specify the name of tag.
82       *
83       * @param tag tag to check
84       * @since 4.2
85       */
86      public void setTag(String tag) {
87          this.tag = tag;
88          tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)");
89      }
90  
91      /**
92       * Setter to specify the regexp to match tag content.
93       *
94       * @param pattern a {@code String} value
95       * @since 4.2
96       */
97      public void setTagFormat(Pattern pattern) {
98          tagFormat = pattern;
99      }
100 
101     /**
102      * Setter to specify the severity level when tag is found and printed.
103      *
104      * @param severity  The new severity level
105      * @see SeverityLevel
106      * @since 4.2
107      */
108     public final void setTagSeverity(SeverityLevel severity) {
109         tagSeverity = severity;
110     }
111 
112     @Override
113     public int[] getDefaultTokens() {
114         return new int[] {
115             TokenTypes.INTERFACE_DEF,
116             TokenTypes.CLASS_DEF,
117             TokenTypes.ENUM_DEF,
118             TokenTypes.ANNOTATION_DEF,
119             TokenTypes.RECORD_DEF,
120         };
121     }
122 
123     @Override
124     public int[] getAcceptableTokens() {
125         return new int[] {
126             TokenTypes.INTERFACE_DEF,
127             TokenTypes.CLASS_DEF,
128             TokenTypes.ENUM_DEF,
129             TokenTypes.ANNOTATION_DEF,
130             TokenTypes.METHOD_DEF,
131             TokenTypes.CTOR_DEF,
132             TokenTypes.ENUM_CONSTANT_DEF,
133             TokenTypes.ANNOTATION_FIELD_DEF,
134             TokenTypes.RECORD_DEF,
135             TokenTypes.COMPACT_CTOR_DEF,
136         };
137     }
138 
139     @Override
140     public boolean isCommentNodesRequired() {
141         return true;
142     }
143 
144     @Override
145     public int[] getRequiredTokens() {
146         return CommonUtil.EMPTY_INT_ARRAY;
147     }
148 
149     @Override
150     public void visitToken(DetailAST ast) {
151         final DetailAST javadoc = getJavadoc(ast);
152 
153         if (javadoc != null) {
154             final String[] cmtLines = LINE_SPLIT_PATTERN
155                     .split(JavadocUtil.getJavadocCommentContent(javadoc));
156 
157             checkTag(javadoc.getLineNo(),
158                     javadoc.getLineNo() + countCommentLines(javadoc),
159                     cmtLines);
160         }
161     }
162 
163     /**
164      * Retrieves the Javadoc comment associated with a given AST node.
165      *
166      * @param ast the AST node (e.g., class, method, constructor) to search above.
167      * @return the {@code DetailAST} representing the Javadoc comment if found and
168      *          valid; {@code null} otherwise.
169      */
170     @Nullable
171     private static DetailAST getJavadoc(DetailAST ast) {
172         // Prefer Javadoc directly above the node
173         DetailAST cmt = ast.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
174         if (cmt == null) {
175             // Check MODIFIERS and TYPE block for comments
176             final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
177             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
178 
179             cmt = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
180             if (cmt == null && type != null) {
181                 cmt = type.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
182             }
183         }
184 
185         final DetailAST javadoc;
186         if (cmt != null && JavadocUtil.isJavadocComment(cmt)) {
187             javadoc = cmt;
188         }
189         else {
190             javadoc = null;
191         }
192 
193         return javadoc;
194     }
195 
196     /**
197      * Counts the number of lines in a block comment.
198      *
199      * @param blockComment the AST node representing the block comment.
200      * @return the number of lines in the comment.
201      */
202     private static int countCommentLines(DetailAST blockComment) {
203         final String content = JavadocUtil.getBlockCommentContent(blockComment);
204         return LINE_SPLIT_PATTERN.split(content).length;
205     }
206 
207     /**
208      * Validates the Javadoc comment against the configured requirements.
209      *
210      * @param astLineNo the line number of the type definition.
211      * @param javadocLineNo the starting line number of the Javadoc comment block.
212      * @param comment the lines of the Javadoc comment block.
213      */
214     private void checkTag(int astLineNo, int javadocLineNo, String... comment) {
215         if (tagRegExp != null) {
216             boolean hasTag = false;
217             for (int i = 0; i < comment.length; i++) {
218                 final String commentValue = comment[i];
219                 final Matcher matcher = tagRegExp.matcher(commentValue);
220                 if (matcher.find()) {
221                     hasTag = true;
222                     final int contentStart = matcher.start(1);
223                     final String content = commentValue.substring(contentStart);
224                     if (tagFormat == null || tagFormat.matcher(content).find()) {
225                         logTag(astLineNo + i, tag, content);
226                     }
227                     else {
228                         log(astLineNo + i, MSG_TAG_FORMAT, tag, tagFormat.pattern());
229                     }
230                 }
231             }
232             if (!hasTag) {
233                 log(javadocLineNo, MSG_MISSING_TAG, tag);
234             }
235         }
236     }
237 
238     /**
239      * Log a message.
240      *
241      * @param line the line number where the violation was found
242      * @param tagName the javadoc tag to be logged
243      * @param tagValue the contents of the tag
244      *
245      * @see java.text.MessageFormat
246      */
247     private void logTag(int line, String tagName, String tagValue) {
248         final String originalSeverity = getSeverity();
249         setSeverity(tagSeverity.getName());
250 
251         log(line, MSG_WRITE_TAG, tagName, tagValue);
252 
253         setSeverity(originalSeverity);
254     }
255 }