View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.api.FileContents;
29  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
30  import com.puppycrawl.tools.checkstyle.api.TextBlock;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  
34  /**
35   * <p>
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   * Property tagSeverity is used for severity of events when the tag exists.
39   * </p>
40   * <ul>
41   * <li>
42   * Property {@code tag} - Specify the name of tag.
43   * Type is {@code java.lang.String}.
44   * Default value is {@code null}.
45   * </li>
46   * <li>
47   * Property {@code tagFormat} - Specify the regexp to match tag content.
48   * Type is {@code java.util.regex.Pattern}.
49   * Default value is {@code null}.
50   * </li>
51   * <li>
52   * Property {@code tagSeverity} - Specify the severity level when tag is found and printed.
53   * Type is {@code com.puppycrawl.tools.checkstyle.api.SeverityLevel}.
54   * Default value is {@code info}.
55   * </li>
56   * <li>
57   * Property {@code tokens} - tokens to check
58   * Type is {@code java.lang.String[]}.
59   * Validation type is {@code tokenSet}.
60   * Default value is:
61   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
62   * INTERFACE_DEF</a>,
63   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
64   * CLASS_DEF</a>,
65   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
66   * ENUM_DEF</a>,
67   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
68   * ANNOTATION_DEF</a>,
69   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
70   * RECORD_DEF</a>.
71   * </li>
72   * </ul>
73   * <p>
74   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
75   * </p>
76   * <p>
77   * Violation Message Keys:
78   * </p>
79   * <ul>
80   * <li>
81   * {@code javadoc.writeTag}
82   * </li>
83   * <li>
84   * {@code type.missingTag}
85   * </li>
86   * <li>
87   * {@code type.tagFormat}
88   * </li>
89   * </ul>
90   *
91   * @since 4.2
92   */
93  @StatelessCheck
94  public class WriteTagCheck
95      extends AbstractCheck {
96  
97      /**
98       * A key is pointing to the warning message text in "messages.properties"
99       * file.
100      */
101     public static final String MSG_MISSING_TAG = "type.missingTag";
102 
103     /**
104      * A key is pointing to the warning message text in "messages.properties"
105      * file.
106      */
107     public static final String MSG_WRITE_TAG = "javadoc.writeTag";
108 
109     /**
110      * A key is pointing to the warning message text in "messages.properties"
111      * file.
112      */
113     public static final String MSG_TAG_FORMAT = "type.tagFormat";
114 
115     /** Compiled regexp to match tag. */
116     private Pattern tagRegExp;
117     /** Specify the regexp to match tag content. */
118     private Pattern tagFormat;
119 
120     /** Specify the name of tag. */
121     private String tag;
122     /** Specify the severity level when tag is found and printed. */
123     private SeverityLevel tagSeverity = SeverityLevel.INFO;
124 
125     /**
126      * Setter to specify the name of tag.
127      *
128      * @param tag tag to check
129      * @since 4.2
130      */
131     public void setTag(String tag) {
132         this.tag = tag;
133         tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)");
134     }
135 
136     /**
137      * Setter to specify the regexp to match tag content.
138      *
139      * @param pattern a {@code String} value
140      * @since 4.2
141      */
142     public void setTagFormat(Pattern pattern) {
143         tagFormat = pattern;
144     }
145 
146     /**
147      * Setter to specify the severity level when tag is found and printed.
148      *
149      * @param severity  The new severity level
150      * @see SeverityLevel
151      * @since 4.2
152      */
153     public final void setTagSeverity(SeverityLevel severity) {
154         tagSeverity = severity;
155     }
156 
157     @Override
158     public int[] getDefaultTokens() {
159         return new int[] {
160             TokenTypes.INTERFACE_DEF,
161             TokenTypes.CLASS_DEF,
162             TokenTypes.ENUM_DEF,
163             TokenTypes.ANNOTATION_DEF,
164             TokenTypes.RECORD_DEF,
165         };
166     }
167 
168     @Override
169     public int[] getAcceptableTokens() {
170         return new int[] {
171             TokenTypes.INTERFACE_DEF,
172             TokenTypes.CLASS_DEF,
173             TokenTypes.ENUM_DEF,
174             TokenTypes.ANNOTATION_DEF,
175             TokenTypes.METHOD_DEF,
176             TokenTypes.CTOR_DEF,
177             TokenTypes.ENUM_CONSTANT_DEF,
178             TokenTypes.ANNOTATION_FIELD_DEF,
179             TokenTypes.RECORD_DEF,
180             TokenTypes.COMPACT_CTOR_DEF,
181         };
182     }
183 
184     @Override
185     public int[] getRequiredTokens() {
186         return CommonUtil.EMPTY_INT_ARRAY;
187     }
188 
189     // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
190     @SuppressWarnings("deprecation")
191     @Override
192     public void visitToken(DetailAST ast) {
193         final FileContents contents = getFileContents();
194         final int lineNo = ast.getLineNo();
195         final TextBlock cmt =
196             contents.getJavadocBefore(lineNo);
197         if (cmt == null) {
198             log(lineNo, MSG_MISSING_TAG, tag);
199         }
200         else {
201             checkTag(lineNo, cmt.getText());
202         }
203     }
204 
205     /**
206      * Verifies that a type definition has a required tag.
207      *
208      * @param lineNo the line number for the type definition.
209      * @param comment the Javadoc comment for the type definition.
210      */
211     private void checkTag(int lineNo, String... comment) {
212         if (tagRegExp != null) {
213             boolean hasTag = false;
214             for (int i = 0; i < comment.length; i++) {
215                 final String commentValue = comment[i];
216                 final Matcher matcher = tagRegExp.matcher(commentValue);
217                 if (matcher.find()) {
218                     hasTag = true;
219                     final int contentStart = matcher.start(1);
220                     final String content = commentValue.substring(contentStart);
221                     if (tagFormat == null || tagFormat.matcher(content).find()) {
222                         logTag(lineNo + i - comment.length, tag, content);
223                     }
224                     else {
225                         log(lineNo + i - comment.length, MSG_TAG_FORMAT, tag, tagFormat.pattern());
226                     }
227                 }
228             }
229             if (!hasTag) {
230                 log(lineNo, MSG_MISSING_TAG, tag);
231             }
232         }
233     }
234 
235     /**
236      * Log a message.
237      *
238      * @param line the line number where the violation was found
239      * @param tagName the javadoc tag to be logged
240      * @param tagValue the contents of the tag
241      *
242      * @see java.text.MessageFormat
243      */
244     private void logTag(int line, String tagName, String tagValue) {
245         final String originalSeverity = getSeverity();
246         setSeverity(tagSeverity.getName());
247 
248         log(line, MSG_WRITE_TAG, tagName, tagValue);
249 
250         setSeverity(originalSeverity);
251     }
252 
253 }