001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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 * <div>
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 * No violation reported in case there is no javadoc.
040 * </div>
041 *
042 * <ul>
043 * <li>
044 * Property {@code tag} - Specify the name of tag.
045 * Type is {@code java.lang.String}.
046 * Default value is {@code null}.
047 * </li>
048 * <li>
049 * Property {@code tagFormat} - Specify the regexp to match tag content.
050 * Type is {@code java.util.regex.Pattern}.
051 * Default value is {@code null}.
052 * </li>
053 * <li>
054 * Property {@code tagSeverity} - Specify the severity level when tag is found and printed.
055 * Type is {@code com.puppycrawl.tools.checkstyle.api.SeverityLevel}.
056 * Default value is {@code info}.
057 * </li>
058 * <li>
059 * Property {@code tokens} - tokens to check
060 * Type is {@code java.lang.String[]}.
061 * Validation type is {@code tokenSet}.
062 * Default value is:
063 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
064 * INTERFACE_DEF</a>,
065 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
066 * CLASS_DEF</a>,
067 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
068 * ENUM_DEF</a>,
069 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
070 * ANNOTATION_DEF</a>,
071 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
072 * RECORD_DEF</a>.
073 * </li>
074 * </ul>
075 *
076 * <p>
077 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
078 * </p>
079 *
080 * <p>
081 * Violation Message Keys:
082 * </p>
083 * <ul>
084 * <li>
085 * {@code javadoc.writeTag}
086 * </li>
087 * <li>
088 * {@code type.missingTag}
089 * </li>
090 * <li>
091 * {@code type.tagFormat}
092 * </li>
093 * </ul>
094 *
095 * @since 4.2
096 */
097@StatelessCheck
098public class WriteTagCheck
099    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}