001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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.Arrays;
023import java.util.Collections;
024import java.util.List;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import com.puppycrawl.tools.checkstyle.StatelessCheck;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.FileContents;
032import com.puppycrawl.tools.checkstyle.api.Scope;
033import com.puppycrawl.tools.checkstyle.api.TextBlock;
034import com.puppycrawl.tools.checkstyle.api.TokenTypes;
035import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
036import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
037import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
038import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
039import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
040
041/**
042 * Checks the Javadoc of a type.
043 *
044 * <p>Does not perform checks for author and version tags for inner classes, as
045 * they should be redundant because of outer class.
046 *
047 */
048@StatelessCheck
049public class JavadocTypeCheck
050    extends AbstractCheck {
051
052    /**
053     * A key is pointing to the warning message text in "messages.properties"
054     * file.
055     */
056    public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag";
057
058    /**
059     * A key is pointing to the warning message text in "messages.properties"
060     * file.
061     */
062    public static final String MSG_TAG_FORMAT = "type.tagFormat";
063
064    /**
065     * A key is pointing to the warning message text in "messages.properties"
066     * file.
067     */
068    public static final String MSG_MISSING_TAG = "type.missingTag";
069
070    /**
071     * A key is pointing to the warning message text in "messages.properties"
072     * file.
073     */
074    public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
075
076    /**
077     * A key is pointing to the warning message text in "messages.properties"
078     * file.
079     */
080    public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
081
082    /** Open angle bracket literal. */
083    private static final String OPEN_ANGLE_BRACKET = "<";
084
085    /** Close angle bracket literal. */
086    private static final String CLOSE_ANGLE_BRACKET = ">";
087
088    /** Pattern to match type name within angle brackets in javadoc param tag. */
089    private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
090            Pattern.compile("\\s*<([^>]+)>.*");
091
092    /** Pattern to split type name field in javadoc param tag. */
093    private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER =
094            Pattern.compile("\\s+");
095
096    /** The scope to check for. */
097    private Scope scope = Scope.PRIVATE;
098    /** The visibility scope where Javadoc comments shouldn't be checked. **/
099    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 violations 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 violations 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 violations 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}