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