View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.List;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.FileContents;
30  import com.puppycrawl.tools.checkstyle.api.Scope;
31  import com.puppycrawl.tools.checkstyle.api.TextBlock;
32  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33  import com.puppycrawl.tools.checkstyle.utils.CheckUtils;
34  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
35  import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
36  import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
37  
38  /**
39   * Checks the Javadoc of a type.
40   *
41   * <p>Does not perform checks for author and version tags for inner classes, as
42   * they should be redundant because of outer class.
43   *
44   * @author Oliver Burn
45   * @author Michael Tamm
46   */
47  @StatelessCheck
48  public class JavadocTypeCheck
49      extends AbstractCheck {
50  
51      /**
52       * A key is pointing to the warning message text in "messages.properties"
53       * file.
54       */
55      public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
56  
57      /**
58       * A key is pointing to the warning message text in "messages.properties"
59       * file.
60       */
61      public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag";
62  
63      /**
64       * A key is pointing to the warning message text in "messages.properties"
65       * file.
66       */
67      public static final String MSG_TAG_FORMAT = "type.tagFormat";
68  
69      /**
70       * A key is pointing to the warning message text in "messages.properties"
71       * file.
72       */
73      public static final String MSG_MISSING_TAG = "type.missingTag";
74  
75      /**
76       * A key is pointing to the warning message text in "messages.properties"
77       * file.
78       */
79      public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
80  
81      /**
82       * A key is pointing to the warning message text in "messages.properties"
83       * file.
84       */
85      public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
86  
87      /** Open angle bracket literal. */
88      private static final String OPEN_ANGLE_BRACKET = "<";
89  
90      /** Close angle bracket literal. */
91      private static final String CLOSE_ANGLE_BRACKET = ">";
92  
93      /** Pattern to match type name within angle brackets in javadoc param tag. */
94      private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
95              Pattern.compile("\\s*<([^>]+)>.*");
96  
97      /** Pattern to split type name field in javadoc param tag. */
98      private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER =
99              Pattern.compile("\\s+");
100 
101     /** The scope to check for. */
102     private Scope scope = Scope.PRIVATE;
103     /** The visibility scope where Javadoc comments shouldn't be checked. **/
104     private Scope excludeScope;
105     /** Compiled regexp to match author tag content. **/
106     private Pattern authorFormat;
107     /** Compiled regexp to match version tag content. **/
108     private Pattern versionFormat;
109     /**
110      * Controls whether to ignore errors when a method has type parameters but
111      * does not have matching param tags in the javadoc. Defaults to false.
112      */
113     private boolean allowMissingParamTags;
114     /** Controls whether to flag errors for unknown tags. Defaults to false. */
115     private boolean allowUnknownTags;
116 
117     /**
118      * Sets the scope to check.
119      * @param scope a scope.
120      */
121     public void setScope(Scope scope) {
122         this.scope = scope;
123     }
124 
125     /**
126      * Set the excludeScope.
127      * @param excludeScope a scope.
128      */
129     public void setExcludeScope(Scope excludeScope) {
130         this.excludeScope = excludeScope;
131     }
132 
133     /**
134      * Set the author tag pattern.
135      * @param pattern a pattern.
136      */
137     public void setAuthorFormat(Pattern pattern) {
138         authorFormat = pattern;
139     }
140 
141     /**
142      * Set the version format pattern.
143      * @param pattern a pattern.
144      */
145     public void setVersionFormat(Pattern pattern) {
146         versionFormat = pattern;
147     }
148 
149     /**
150      * Controls whether to allow a type which has type parameters to
151      * omit matching param tags in the javadoc. Defaults to false.
152      *
153      * @param flag a {@code Boolean} value
154      */
155     public void setAllowMissingParamTags(boolean flag) {
156         allowMissingParamTags = flag;
157     }
158 
159     /**
160      * Controls whether to flag errors for unknown tags. Defaults to false.
161      * @param flag a {@code Boolean} value
162      */
163     public void setAllowUnknownTags(boolean flag) {
164         allowUnknownTags = flag;
165     }
166 
167     @Override
168     public int[] getDefaultTokens() {
169         return getAcceptableTokens();
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         };
180     }
181 
182     @Override
183     public int[] getRequiredTokens() {
184         return CommonUtils.EMPTY_INT_ARRAY;
185     }
186 
187     @Override
188     public void visitToken(DetailAST ast) {
189         if (shouldCheck(ast)) {
190             final FileContents contents = getFileContents();
191             final int lineNo = ast.getLineNo();
192             final TextBlock textBlock = contents.getJavadocBefore(lineNo);
193             if (textBlock == null) {
194                 log(lineNo, MSG_JAVADOC_MISSING);
195             }
196             else {
197                 final List<JavadocTag> tags = getJavadocTags(textBlock);
198                 if (ScopeUtils.isOuterMostType(ast)) {
199                     // don't check author/version for inner classes
200                     checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(),
201                             authorFormat);
202                     checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(),
203                             versionFormat);
204                 }
205 
206                 final List<String> typeParamNames =
207                     CheckUtils.getTypeParameterNames(ast);
208 
209                 if (!allowMissingParamTags) {
210                     //Check type parameters that should exist, do
211                     for (final String typeParamName : typeParamNames) {
212                         checkTypeParamTag(
213                             lineNo, tags, typeParamName);
214                     }
215                 }
216 
217                 checkUnusedTypeParamTags(tags, typeParamNames);
218             }
219         }
220     }
221 
222     /**
223      * Whether we should check this node.
224      * @param ast a given node.
225      * @return whether we should check a given node.
226      */
227     private boolean shouldCheck(final DetailAST ast) {
228         final Scope customScope;
229 
230         if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) {
231             customScope = Scope.PUBLIC;
232         }
233         else {
234             final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
235             customScope = ScopeUtils.getScopeFromMods(mods);
236         }
237         final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast);
238 
239         return customScope.isIn(scope)
240             && (surroundingScope == null || surroundingScope.isIn(scope))
241             && (excludeScope == null
242                 || !customScope.isIn(excludeScope)
243                 || surroundingScope != null
244                 && !surroundingScope.isIn(excludeScope));
245     }
246 
247     /**
248      * Gets all standalone tags from a given javadoc.
249      * @param textBlock the Javadoc comment to process.
250      * @return all standalone tags from the given javadoc.
251      */
252     private List<JavadocTag> getJavadocTags(TextBlock textBlock) {
253         final JavadocTags tags = JavadocUtils.getJavadocTags(textBlock,
254             JavadocUtils.JavadocTagType.BLOCK);
255         if (!allowUnknownTags) {
256             for (final InvalidJavadocTag tag : tags.getInvalidTags()) {
257                 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG,
258                     tag.getName());
259             }
260         }
261         return tags.getValidTags();
262     }
263 
264     /**
265      * Verifies that a type definition has a required tag.
266      * @param lineNo the line number for the type definition.
267      * @param tags tags from the Javadoc comment for the type definition.
268      * @param tagName the required tag name.
269      * @param formatPattern regexp for the tag value.
270      */
271     private void checkTag(int lineNo, List<JavadocTag> tags, String tagName,
272                           Pattern formatPattern) {
273         if (formatPattern != null) {
274             int tagCount = 0;
275             final String tagPrefix = "@";
276             for (int i = tags.size() - 1; i >= 0; i--) {
277                 final JavadocTag tag = tags.get(i);
278                 if (tag.getTagName().equals(tagName)) {
279                     tagCount++;
280                     if (!formatPattern.matcher(tag.getFirstArg()).find()) {
281                         log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern());
282                     }
283                 }
284             }
285             if (tagCount == 0) {
286                 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName);
287             }
288         }
289     }
290 
291     /**
292      * Verifies that a type definition has the specified param tag for
293      * the specified type parameter name.
294      * @param lineNo the line number for the type definition.
295      * @param tags tags from the Javadoc comment for the type definition.
296      * @param typeParamName the name of the type parameter
297      */
298     private void checkTypeParamTag(final int lineNo,
299             final List<JavadocTag> tags, final String typeParamName) {
300         boolean found = false;
301         for (int i = tags.size() - 1; i >= 0; i--) {
302             final JavadocTag tag = tags.get(i);
303             if (tag.isParamTag()
304                 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET
305                         + typeParamName + CLOSE_ANGLE_BRACKET) == 0) {
306                 found = true;
307                 break;
308             }
309         }
310         if (!found) {
311             log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText()
312                 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
313         }
314     }
315 
316     /**
317      * Checks for unused param tags for type parameters.
318      * @param tags tags from the Javadoc comment for the type definition.
319      * @param typeParamNames names of type parameters
320      */
321     private void checkUnusedTypeParamTags(
322         final List<JavadocTag> tags,
323         final List<String> typeParamNames) {
324         for (int i = tags.size() - 1; i >= 0; i--) {
325             final JavadocTag tag = tags.get(i);
326             if (tag.isParamTag()) {
327 
328                 final String typeParamName = extractTypeParamNameFromTag(tag);
329 
330                 if (!typeParamNames.contains(typeParamName)) {
331                     log(tag.getLineNo(), tag.getColumnNo(),
332                             MSG_UNUSED_TAG,
333                             JavadocTagInfo.PARAM.getText(),
334                             OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
335                 }
336             }
337         }
338     }
339 
340     /**
341      * Extracts type parameter name from tag.
342      * @param tag javadoc tag to extract parameter name
343      * @return extracts type parameter name from tag
344      */
345     private static String extractTypeParamNameFromTag(JavadocTag tag) {
346         final String typeParamName;
347         final Matcher matchInAngleBrackets =
348                 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg());
349         if (matchInAngleBrackets.find()) {
350             typeParamName = matchInAngleBrackets.group(1).trim();
351         }
352         else {
353             typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0];
354         }
355         return typeParamName;
356     }
357 }