001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.javadoc;
021
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.FileContents;
029import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
030import com.puppycrawl.tools.checkstyle.api.TextBlock;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
033
034/**
035 * <p>
036 * Requires user defined Javadoc tag to be present in Javadoc comment with defined format.
037 * To define the format for a tag, set property tagFormat to a regular expression.
038 * Property tagSeverity is used for severity of events when the tag exists.
039 * </p>
040 * <ul>
041 * <li>
042 * Property {@code tag} - Specify the name of tag.
043 * Type is {@code java.lang.String}.
044 * Default value is {@code null}.
045 * </li>
046 * <li>
047 * Property {@code tagFormat} - Specify the regexp to match tag content.
048 * Type is {@code java.util.regex.Pattern}.
049 * Default value is {@code null}.
050 * </li>
051 * <li>
052 * Property {@code tagSeverity} - Specify the severity level when tag is found and printed.
053 * Type is {@code com.puppycrawl.tools.checkstyle.api.SeverityLevel}.
054 * Default value is {@code info}.
055 * </li>
056 * <li>
057 * Property {@code tokens} - tokens to check
058 * Type is {@code java.lang.String[]}.
059 * Validation type is {@code tokenSet}.
060 * Default value is:
061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
062 * INTERFACE_DEF</a>,
063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
064 * CLASS_DEF</a>,
065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
066 * ENUM_DEF</a>,
067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
068 * ANNOTATION_DEF</a>,
069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
070 * RECORD_DEF</a>.
071 * </li>
072 * </ul>
073 * <p>
074 * Example of default Check configuration that do nothing.
075 * </p>
076 * <pre>
077 * &lt;module name="WriteTag"/&gt;
078 * </pre>
079 * <p>
080 * Example:
081 * </p>
082 * <pre>
083 * &#47;**
084 * * Some class
085 * *&#47;
086 * public class Test {
087 *   &#47;** some doc *&#47;
088 *   void foo() {}
089 * }
090 * </pre>
091 * <p>
092 * To configure Check to demand some special tag (for example {@code &#64;since})
093 * to be present on classes javadoc.
094 * </p>
095 * <pre>
096 * &lt;module name="WriteTag"&gt;
097 *   &lt;property name="tag" value="@since"/&gt;
098 * &lt;/module&gt;
099 * </pre>
100 * <p>
101 * Example:
102 * </p>
103 * <pre>
104 * &#47;**
105 * * Some class
106 * *&#47;
107 * public class Test { // violation as required tag is missed
108 *   &#47;** some doc *&#47;
109 *   void foo() {} // OK, as methods are not checked by default
110 * }
111 * </pre>
112 * <p>
113 * To configure Check to demand some special tag (for example {@code &#64;since})
114 * to be present on method javadocs also in addition to default tokens.
115 * </p>
116 * <pre>
117 * &lt;module name="WriteTag"&gt;
118 *   &lt;property name="tag" value="@since"/&gt;
119 *   &lt;property name="tokens"
120 *          value="INTERFACE_DEF, CLASS_DEF, ENUM_DEF, ANNOTATION_DEF, RECORD_DEF, METHOD_DEF" /&gt;
121 * &lt;/module&gt;
122 * </pre>
123 * <p>
124 * Example:
125 * </p>
126 * <pre>
127 * &#47;**
128 * * Some class
129 * *&#47;
130 * public class Test { // violation as required tag is missed
131 *   &#47;** some doc *&#47;
132 *   void foo() {} // violation as required tag is missed
133 * }
134 * </pre>
135 * <p>
136 * To configure Check to demand {@code &#64;since} tag
137 * to be present with digital value on method javadocs also in addition to default tokens.
138 * Attention: usage of non "ignore" in tagSeverity will print violation with such severity
139 * on each presence of such tag.
140 * </p>
141 * <pre>
142 * &lt;module name="WriteTag"&gt;
143 *   &lt;property name="tag" value="@since"/&gt;
144 *   &lt;property name="tokens"
145 *          value="INTERFACE_DEF, CLASS_DEF, ENUM_DEF, ANNOTATION_DEF, RECORD_DEF, METHOD_DEF" /&gt;
146 *   &lt;property name="tagFormat" value="[1-9\.]"/&gt;
147 *   &lt;property name="tagSeverity" value="ignore"/&gt;
148 * &lt;/module&gt;
149 * </pre>
150 * <p>
151 * Example:
152 * </p>
153 * <pre>
154 * &#47;**
155 * * Some class
156 * * &#64;since 1.2
157 * *&#47;
158 * public class Test {
159 *   &#47;** some doc
160 *   * &#64;since violation
161 *   *&#47;
162 *   void foo() {}
163 * }
164 * </pre>
165 * <p>
166 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
167 * </p>
168 * <p>
169 * Violation Message Keys:
170 * </p>
171 * <ul>
172 * <li>
173 * {@code javadoc.writeTag}
174 * </li>
175 * <li>
176 * {@code type.missingTag}
177 * </li>
178 * <li>
179 * {@code type.tagFormat}
180 * </li>
181 * </ul>
182 *
183 * @since 4.2
184 */
185@StatelessCheck
186public class WriteTagCheck
187    extends AbstractCheck {
188
189    /**
190     * A key is pointing to the warning message text in "messages.properties"
191     * file.
192     */
193    public static final String MSG_MISSING_TAG = "type.missingTag";
194
195    /**
196     * A key is pointing to the warning message text in "messages.properties"
197     * file.
198     */
199    public static final String MSG_WRITE_TAG = "javadoc.writeTag";
200
201    /**
202     * A key is pointing to the warning message text in "messages.properties"
203     * file.
204     */
205    public static final String MSG_TAG_FORMAT = "type.tagFormat";
206
207    /** Compiled regexp to match tag. */
208    private Pattern tagRegExp;
209    /** Specify the regexp to match tag content. */
210    private Pattern tagFormat;
211
212    /** Specify the name of tag. */
213    private String tag;
214    /** Specify the severity level when tag is found and printed. */
215    private SeverityLevel tagSeverity = SeverityLevel.INFO;
216
217    /**
218     * Setter to specify the name of tag.
219     *
220     * @param tag tag to check
221     */
222    public void setTag(String tag) {
223        this.tag = tag;
224        tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)");
225    }
226
227    /**
228     * Setter to specify the regexp to match tag content.
229     *
230     * @param pattern a {@code String} value
231     */
232    public void setTagFormat(Pattern pattern) {
233        tagFormat = pattern;
234    }
235
236    /**
237     * Setter to specify the severity level when tag is found and printed.
238     *
239     * @param severity  The new severity level
240     * @see SeverityLevel
241     */
242    public final void setTagSeverity(SeverityLevel severity) {
243        tagSeverity = severity;
244    }
245
246    @Override
247    public int[] getDefaultTokens() {
248        return new int[] {
249            TokenTypes.INTERFACE_DEF,
250            TokenTypes.CLASS_DEF,
251            TokenTypes.ENUM_DEF,
252            TokenTypes.ANNOTATION_DEF,
253            TokenTypes.RECORD_DEF,
254        };
255    }
256
257    @Override
258    public int[] getAcceptableTokens() {
259        return new int[] {
260            TokenTypes.INTERFACE_DEF,
261            TokenTypes.CLASS_DEF,
262            TokenTypes.ENUM_DEF,
263            TokenTypes.ANNOTATION_DEF,
264            TokenTypes.METHOD_DEF,
265            TokenTypes.CTOR_DEF,
266            TokenTypes.ENUM_CONSTANT_DEF,
267            TokenTypes.ANNOTATION_FIELD_DEF,
268            TokenTypes.RECORD_DEF,
269            TokenTypes.COMPACT_CTOR_DEF,
270        };
271    }
272
273    @Override
274    public int[] getRequiredTokens() {
275        return CommonUtil.EMPTY_INT_ARRAY;
276    }
277
278    @Override
279    public void visitToken(DetailAST ast) {
280        final FileContents contents = getFileContents();
281        final int lineNo = ast.getLineNo();
282        final TextBlock cmt =
283            contents.getJavadocBefore(lineNo);
284        if (cmt == null) {
285            log(lineNo, MSG_MISSING_TAG, tag);
286        }
287        else {
288            checkTag(lineNo, cmt.getText());
289        }
290    }
291
292    /**
293     * Verifies that a type definition has a required tag.
294     *
295     * @param lineNo the line number for the type definition.
296     * @param comment the Javadoc comment for the type definition.
297     */
298    private void checkTag(int lineNo, String... comment) {
299        if (tagRegExp != null) {
300            boolean hasTag = false;
301            for (int i = 0; i < comment.length; i++) {
302                final String commentValue = comment[i];
303                final Matcher matcher = tagRegExp.matcher(commentValue);
304                if (matcher.find()) {
305                    hasTag = true;
306                    final int contentStart = matcher.start(1);
307                    final String content = commentValue.substring(contentStart);
308                    if (tagFormat == null || tagFormat.matcher(content).find()) {
309                        logTag(lineNo + i - comment.length, tag, content);
310                    }
311                    else {
312                        log(lineNo + i - comment.length, MSG_TAG_FORMAT, tag, tagFormat.pattern());
313                    }
314                }
315            }
316            if (!hasTag) {
317                log(lineNo, MSG_MISSING_TAG, tag);
318            }
319        }
320    }
321
322    /**
323     * Log a message.
324     *
325     * @param line the line number where the violation was found
326     * @param tagName the javadoc tag to be logged
327     * @param tagValue the contents of the tag
328     *
329     * @see java.text.MessageFormat
330     */
331    private void logTag(int line, String tagName, String tagValue) {
332        final String originalSeverity = getSeverity();
333        setSeverity(tagSeverity.getName());
334
335        log(line, MSG_WRITE_TAG, tagName, tagValue);
336
337        setSeverity(originalSeverity);
338    }
339
340}