1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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   * Outputs a JavaDoc tag as information. Can be used e.g. with the stylesheets
37   * that sort the report by author name.
38   * To define the format for a tag, set property tagFormat to a
39   * regular expression.
40   * This check uses two different severity levels. The normal one is used for
41   * reporting when the tag is missing. The additional one (tagSeverity) is used
42   * for the level of reporting when the tag exists. The default value for
43   * tagSeverity is info.
44   * </p>
45   * <p> An example of how to configure the check for printing author name is:
46   *</p>
47   * <pre>
48   * &lt;module name="WriteTag"&gt;
49   *    &lt;property name="tag" value="@author"/&gt;
50   *    &lt;property name="tagFormat" value="\S"/&gt;
51   * &lt;/module&gt;
52   * </pre>
53   * <p> An example of how to configure the check to print warnings if an
54   * "@incomplete" tag is found, and not print anything if it is not found:
55   *</p>
56   * <pre>
57   * &lt;module name="WriteTag"&gt;
58   *    &lt;property name="tag" value="@incomplete"/&gt;
59   *    &lt;property name="tagFormat" value="\S"/&gt;
60   *    &lt;property name="severity" value="ignore"/&gt;
61   *    &lt;property name="tagSeverity" value="warning"/&gt;
62   * &lt;/module&gt;
63   * </pre>
64   *
65   */
66  @StatelessCheck
67  public class WriteTagCheck
68      extends AbstractCheck {
69  
70      /**
71       * A key is pointing to the warning message text in "messages.properties"
72       * file.
73       */
74      public static final String MSG_MISSING_TAG = "type.missingTag";
75  
76      /**
77       * A key is pointing to the warning message text in "messages.properties"
78       * file.
79       */
80      public static final String MSG_WRITE_TAG = "javadoc.writeTag";
81  
82      /**
83       * A key is pointing to the warning message text in "messages.properties"
84       * file.
85       */
86      public static final String MSG_TAG_FORMAT = "type.tagFormat";
87  
88      /** Compiled regexp to match tag. **/
89      private Pattern tagRegExp;
90      /** Compiled regexp to match tag content. **/
91      private Pattern tagFormat;
92  
93      /** Regexp to match tag. */
94      private String tag;
95      /** The severity level of found tag reports. */
96      private SeverityLevel tagSeverity = SeverityLevel.INFO;
97  
98      /**
99       * Sets the tag to check.
100      * @param tag tag to check
101      */
102     public void setTag(String tag) {
103         this.tag = tag;
104         tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)");
105     }
106 
107     /**
108      * Set the tag format.
109      * @param pattern a {@code String} value
110      */
111     public void setTagFormat(Pattern pattern) {
112         tagFormat = pattern;
113     }
114 
115     /**
116      * Sets the tag severity level.
117      *
118      * @param severity  The new severity level
119      * @see SeverityLevel
120      */
121     public final void setTagSeverity(SeverityLevel severity) {
122         tagSeverity = severity;
123     }
124 
125     @Override
126     public int[] getDefaultTokens() {
127         return new int[] {TokenTypes.INTERFACE_DEF,
128                           TokenTypes.CLASS_DEF,
129                           TokenTypes.ENUM_DEF,
130                           TokenTypes.ANNOTATION_DEF,
131         };
132     }
133 
134     @Override
135     public int[] getAcceptableTokens() {
136         return new int[] {TokenTypes.INTERFACE_DEF,
137                           TokenTypes.CLASS_DEF,
138                           TokenTypes.ENUM_DEF,
139                           TokenTypes.ANNOTATION_DEF,
140                           TokenTypes.METHOD_DEF,
141                           TokenTypes.CTOR_DEF,
142                           TokenTypes.ENUM_CONSTANT_DEF,
143                           TokenTypes.ANNOTATION_FIELD_DEF,
144         };
145     }
146 
147     @Override
148     public int[] getRequiredTokens() {
149         return CommonUtil.EMPTY_INT_ARRAY;
150     }
151 
152     @Override
153     public void visitToken(DetailAST ast) {
154         final FileContents contents = getFileContents();
155         final int lineNo = ast.getLineNo();
156         final TextBlock cmt =
157             contents.getJavadocBefore(lineNo);
158         if (cmt == null) {
159             log(lineNo, MSG_MISSING_TAG, tag);
160         }
161         else {
162             checkTag(lineNo, cmt.getText());
163         }
164     }
165 
166     /**
167      * Verifies that a type definition has a required tag.
168      * @param lineNo the line number for the type definition.
169      * @param comment the Javadoc comment for the type definition.
170      */
171     private void checkTag(int lineNo, String... comment) {
172         if (tagRegExp != null) {
173             boolean hasTag = false;
174             for (int i = 0; i < comment.length; i++) {
175                 final String commentValue = comment[i];
176                 final Matcher matcher = tagRegExp.matcher(commentValue);
177                 if (matcher.find()) {
178                     hasTag = true;
179                     final int contentStart = matcher.start(1);
180                     final String content = commentValue.substring(contentStart);
181                     if (tagFormat == null || tagFormat.matcher(content).find()) {
182                         logTag(lineNo + i - comment.length, tag, content);
183                     }
184                     else {
185                         log(lineNo + i - comment.length, MSG_TAG_FORMAT, tag, tagFormat.pattern());
186                     }
187                 }
188             }
189             if (!hasTag) {
190                 log(lineNo, MSG_MISSING_TAG, tag);
191             }
192         }
193     }
194 
195     /**
196      * Log a message.
197      *
198      * @param line the line number where the error was found
199      * @param tagName the javadoc tag to be logged
200      * @param tagValue the contents of the tag
201      *
202      * @see java.text.MessageFormat
203      */
204     private void logTag(int line, String tagName, String tagValue) {
205         final String originalSeverity = getSeverity();
206         setSeverity(tagSeverity.getName());
207 
208         log(line, MSG_WRITE_TAG, tagName, tagValue);
209 
210         setSeverity(originalSeverity);
211     }
212 
213 }