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