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_JAVADOC_MISSING = "javadoc.missing";
057
058    /**
059     * A key is pointing to the warning message text in "messages.properties"
060     * file.
061     */
062    public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag";
063
064    /**
065     * A key is pointing to the warning message text in "messages.properties"
066     * file.
067     */
068    public static final String MSG_TAG_FORMAT = "type.tagFormat";
069
070    /**
071     * A key is pointing to the warning message text in "messages.properties"
072     * file.
073     */
074    public static final String MSG_MISSING_TAG = "type.missingTag";
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 = "javadoc.unusedTag";
081
082    /**
083     * A key is pointing to the warning message text in "messages.properties"
084     * file.
085     */
086    public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
087
088    /** Open angle bracket literal. */
089    private static final String OPEN_ANGLE_BRACKET = "<";
090
091    /** Close angle bracket literal. */
092    private static final String CLOSE_ANGLE_BRACKET = ">";
093
094    /** Pattern to match type name within angle brackets in javadoc param tag. */
095    private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
096            Pattern.compile("\\s*<([^>]+)>.*");
097
098    /** Pattern to split type name field in javadoc param tag. */
099    private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER =
100            Pattern.compile("\\s+");
101
102    /** The scope to check for. */
103    private Scope scope = Scope.PRIVATE;
104    /** The visibility scope where Javadoc comments shouldn't be checked. **/
105    private Scope excludeScope;
106    /** Compiled regexp to match author tag content. **/
107    private Pattern authorFormat;
108    /** Compiled regexp to match version tag content. **/
109    private Pattern versionFormat;
110    /**
111     * Controls whether to ignore errors when a method has type parameters but
112     * does not have matching param tags in the javadoc. Defaults to false.
113     */
114    private boolean allowMissingParamTags;
115    /** Controls whether to flag errors for unknown tags. Defaults to false. */
116    private boolean allowUnknownTags;
117
118    /** List of annotations that allow missed documentation. */
119    private List<String> allowedAnnotations = Collections.singletonList("Generated");
120
121    /**
122     * Sets the scope to check.
123     * @param scope a scope.
124     */
125    public void setScope(Scope scope) {
126        this.scope = scope;
127    }
128
129    /**
130     * Set the excludeScope.
131     * @param excludeScope a scope.
132     */
133    public void setExcludeScope(Scope excludeScope) {
134        this.excludeScope = excludeScope;
135    }
136
137    /**
138     * Set the author tag pattern.
139     * @param pattern a pattern.
140     */
141    public void setAuthorFormat(Pattern pattern) {
142        authorFormat = pattern;
143    }
144
145    /**
146     * Set the version format pattern.
147     * @param pattern a pattern.
148     */
149    public void setVersionFormat(Pattern pattern) {
150        versionFormat = pattern;
151    }
152
153    /**
154     * Controls whether to allow a type which has type parameters to
155     * omit matching param tags in the javadoc. Defaults to false.
156     *
157     * @param flag a {@code Boolean} value
158     */
159    public void setAllowMissingParamTags(boolean flag) {
160        allowMissingParamTags = flag;
161    }
162
163    /**
164     * Controls whether to flag errors for unknown tags. Defaults to false.
165     * @param flag a {@code Boolean} value
166     */
167    public void setAllowUnknownTags(boolean flag) {
168        allowUnknownTags = flag;
169    }
170
171    /**
172     * Sets list of annotations.
173     * @param userAnnotations user's value.
174     */
175    public void setAllowedAnnotations(String... userAnnotations) {
176        allowedAnnotations = Arrays.asList(userAnnotations);
177    }
178
179    @Override
180    public int[] getDefaultTokens() {
181        return getAcceptableTokens();
182    }
183
184    @Override
185    public int[] getAcceptableTokens() {
186        return new int[] {
187            TokenTypes.INTERFACE_DEF,
188            TokenTypes.CLASS_DEF,
189            TokenTypes.ENUM_DEF,
190            TokenTypes.ANNOTATION_DEF,
191        };
192    }
193
194    @Override
195    public int[] getRequiredTokens() {
196        return CommonUtil.EMPTY_INT_ARRAY;
197    }
198
199    @Override
200    public void visitToken(DetailAST ast) {
201        if (shouldCheck(ast)) {
202            final FileContents contents = getFileContents();
203            final int lineNo = ast.getLineNo();
204            final TextBlock textBlock = contents.getJavadocBefore(lineNo);
205            if (textBlock == null) {
206                log(lineNo, MSG_JAVADOC_MISSING);
207            }
208            else {
209                final List<JavadocTag> tags = getJavadocTags(textBlock);
210                if (ScopeUtil.isOuterMostType(ast)) {
211                    // don't check author/version for inner classes
212                    checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(),
213                            authorFormat);
214                    checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(),
215                            versionFormat);
216                }
217
218                final List<String> typeParamNames =
219                    CheckUtil.getTypeParameterNames(ast);
220
221                if (!allowMissingParamTags) {
222                    //Check type parameters that should exist, do
223                    for (final String typeParamName : typeParamNames) {
224                        checkTypeParamTag(
225                            lineNo, tags, typeParamName);
226                    }
227                }
228
229                checkUnusedTypeParamTags(tags, typeParamNames);
230            }
231        }
232    }
233
234    /**
235     * Whether we should check this node.
236     * @param ast a given node.
237     * @return whether we should check a given node.
238     */
239    private boolean shouldCheck(final DetailAST ast) {
240        final Scope customScope;
241
242        if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
243            customScope = Scope.PUBLIC;
244        }
245        else {
246            final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
247            customScope = ScopeUtil.getScopeFromMods(mods);
248        }
249        final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
250
251        return customScope.isIn(scope)
252            && (surroundingScope == null || surroundingScope.isIn(scope))
253            && (excludeScope == null
254                || !customScope.isIn(excludeScope)
255                || surroundingScope != null
256                && !surroundingScope.isIn(excludeScope))
257            && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
258    }
259
260    /**
261     * Gets all standalone tags from a given javadoc.
262     * @param textBlock the Javadoc comment to process.
263     * @return all standalone tags from the given javadoc.
264     */
265    private List<JavadocTag> getJavadocTags(TextBlock textBlock) {
266        final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock,
267            JavadocUtil.JavadocTagType.BLOCK);
268        if (!allowUnknownTags) {
269            for (final InvalidJavadocTag tag : tags.getInvalidTags()) {
270                log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG,
271                    tag.getName());
272            }
273        }
274        return tags.getValidTags();
275    }
276
277    /**
278     * Verifies that a type definition has a required tag.
279     * @param lineNo the line number for the type definition.
280     * @param tags tags from the Javadoc comment for the type definition.
281     * @param tagName the required tag name.
282     * @param formatPattern regexp for the tag value.
283     */
284    private void checkTag(int lineNo, List<JavadocTag> tags, String tagName,
285                          Pattern formatPattern) {
286        if (formatPattern != null) {
287            boolean hasTag = false;
288            final String tagPrefix = "@";
289            for (int i = tags.size() - 1; i >= 0; i--) {
290                final JavadocTag tag = tags.get(i);
291                if (tag.getTagName().equals(tagName)) {
292                    hasTag = true;
293                    if (!formatPattern.matcher(tag.getFirstArg()).find()) {
294                        log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern());
295                    }
296                }
297            }
298            if (!hasTag) {
299                log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName);
300            }
301        }
302    }
303
304    /**
305     * Verifies that a type definition has the specified param tag for
306     * the specified type parameter name.
307     * @param lineNo the line number for the type definition.
308     * @param tags tags from the Javadoc comment for the type definition.
309     * @param typeParamName the name of the type parameter
310     */
311    private void checkTypeParamTag(final int lineNo,
312            final List<JavadocTag> tags, final String typeParamName) {
313        boolean found = false;
314        for (int i = tags.size() - 1; i >= 0; i--) {
315            final JavadocTag tag = tags.get(i);
316            if (tag.isParamTag()
317                && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET
318                        + typeParamName + CLOSE_ANGLE_BRACKET) == 0) {
319                found = true;
320                break;
321            }
322        }
323        if (!found) {
324            log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText()
325                + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
326        }
327    }
328
329    /**
330     * Checks for unused param tags for type parameters.
331     * @param tags tags from the Javadoc comment for the type definition.
332     * @param typeParamNames names of type parameters
333     */
334    private void checkUnusedTypeParamTags(
335        final List<JavadocTag> tags,
336        final List<String> typeParamNames) {
337        for (int i = tags.size() - 1; i >= 0; i--) {
338            final JavadocTag tag = tags.get(i);
339            if (tag.isParamTag()) {
340                final String typeParamName = extractTypeParamNameFromTag(tag);
341
342                if (!typeParamNames.contains(typeParamName)) {
343                    log(tag.getLineNo(), tag.getColumnNo(),
344                            MSG_UNUSED_TAG,
345                            JavadocTagInfo.PARAM.getText(),
346                            OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
347                }
348            }
349        }
350    }
351
352    /**
353     * Extracts type parameter name from tag.
354     * @param tag javadoc tag to extract parameter name
355     * @return extracts type parameter name from tag
356     */
357    private static String extractTypeParamNameFromTag(JavadocTag tag) {
358        final String typeParamName;
359        final Matcher matchInAngleBrackets =
360                TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg());
361        if (matchInAngleBrackets.find()) {
362            typeParamName = matchInAngleBrackets.group(1).trim();
363        }
364        else {
365            typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0];
366        }
367        return typeParamName;
368    }
369
370}