//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.checks.javadoc; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.FileContents; import com.puppycrawl.tools.checkstyle.api.Scope; import com.puppycrawl.tools.checkstyle.api.TextBlock; import com.puppycrawl.tools.checkstyle.api.TokenTypes; import com.puppycrawl.tools.checkstyle.utils.CheckUtils; import com.puppycrawl.tools.checkstyle.utils.CommonUtils; import com.puppycrawl.tools.checkstyle.utils.JavadocUtils; import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; /** * Checks the Javadoc of a type. * * <p>Does not perform checks for author and version tags for inner classes, as * they should be redundant because of outer class. * * @author Oliver Burn * @author Michael Tamm */ public class JavadocTypeCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_JAVADOC_MISSING = "javadoc.missing"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_TAG_FORMAT = "type.tagFormat"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_MISSING_TAG = "type.missingTag"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_UNUSED_TAG = "javadoc.unusedTag"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral"; /** Open angle bracket literal. */ private static final String OPEN_ANGLE_BRACKET = "<"; /** Close angle bracket literal. */ private static final String CLOSE_ANGLE_BRACKET = ">"; /** Pattern to match type name within angle brackets in javadoc param tag. */ private static final Pattern TYPE_NAME_IN_JAVADOC_TAG = Pattern.compile("\\s*<([^>]+)>.*"); /** Pattern to split type name field in javadoc param tag. */ private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER = Pattern.compile("\\s+"); /** The scope to check for. */ private Scope scope = Scope.PRIVATE; /** The visibility scope where Javadoc comments shouldn't be checked. **/ private Scope excludeScope; /** Compiled regexp to match author tag content. **/ private Pattern authorFormat; /** Compiled regexp to match version tag content. **/ private Pattern versionFormat; /** * Controls whether to ignore errors when a method has type parameters but * does not have matching param tags in the javadoc. Defaults to false. */ private boolean allowMissingParamTags; /** Controls whether to flag errors for unknown tags. Defaults to false. */ private boolean allowUnknownTags; /** * Sets the scope to check. * @param scope a scope. */ public void setScope(Scope scope) { this.scope = scope; } /** * Set the excludeScope. * @param excludeScope a scope. */ public void setExcludeScope(Scope excludeScope) { this.excludeScope = excludeScope; } /** * Set the author tag pattern. * @param pattern a pattern. */ public void setAuthorFormat(Pattern pattern) { authorFormat = pattern; } /** * Set the version format pattern. * @param pattern a pattern. */ public void setVersionFormat(Pattern pattern) { versionFormat = pattern; } /** * Controls whether to allow a type which has type parameters to * omit matching param tags in the javadoc. Defaults to false. * * @param flag a {@code Boolean} value */ public void setAllowMissingParamTags(boolean flag) { allowMissingParamTags = flag; } /** * Controls whether to flag errors for unknown tags. Defaults to false. * @param flag a {@code Boolean} value */ public void setAllowUnknownTags(boolean flag) { allowUnknownTags = flag; } @Override public int[] getDefaultTokens() { return getAcceptableTokens(); } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF, TokenTypes.ANNOTATION_DEF, }; } @Override public int[] getRequiredTokens() { return CommonUtils.EMPTY_INT_ARRAY; } @Override public void visitToken(DetailAST ast) { if (shouldCheck(ast)) { final FileContents contents = getFileContents(); final int lineNo = ast.getLineNo(); final TextBlock textBlock = contents.getJavadocBefore(lineNo); if (textBlock == null) { log(lineNo, MSG_JAVADOC_MISSING); } else { final List<JavadocTag> tags = getJavadocTags(textBlock); if (ScopeUtils.isOuterMostType(ast)) { // don't check author/version for inner classes checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(), authorFormat); checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(), versionFormat); } final List<String> typeParamNames = CheckUtils.getTypeParameterNames(ast); if (!allowMissingParamTags) { //Check type parameters that should exist, do for (final String typeParamName : typeParamNames) { checkTypeParamTag( lineNo, tags, typeParamName); } } checkUnusedTypeParamTags(tags, typeParamNames); } } } /** * Whether we should check this node. * @param ast a given node. * @return whether we should check a given node. */ private boolean shouldCheck(final DetailAST ast) { final Scope customScope; if (ScopeUtils.isInInterfaceOrAnnotationBlock(ast)) { customScope = Scope.PUBLIC; } else { final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); customScope = ScopeUtils.getScopeFromMods(mods); } final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); return customScope.isIn(scope) && (surroundingScope == null || surroundingScope.isIn(scope)) && (excludeScope == null || !customScope.isIn(excludeScope) || surroundingScope != null && !surroundingScope.isIn(excludeScope)); } /** * Gets all standalone tags from a given javadoc. * @param textBlock the Javadoc comment to process. * @return all standalone tags from the given javadoc. */ private List<JavadocTag> getJavadocTags(TextBlock textBlock) { final JavadocTags tags = JavadocUtils.getJavadocTags(textBlock, JavadocUtils.JavadocTagType.BLOCK); if (!allowUnknownTags) { for (final InvalidJavadocTag tag : tags.getInvalidTags()) { log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, tag.getName()); } } return tags.getValidTags(); } /** * Verifies that a type definition has a required tag. * @param lineNo the line number for the type definition. * @param tags tags from the Javadoc comment for the type definition. * @param tagName the required tag name. * @param formatPattern regexp for the tag value. */ private void checkTag(int lineNo, List<JavadocTag> tags, String tagName, Pattern formatPattern) { if (formatPattern != null) { int tagCount = 0; final String tagPrefix = "@"; for (int i = tags.size() - 1; i >= 0; i--) { final JavadocTag tag = tags.get(i); if (tag.getTagName().equals(tagName)) { tagCount++; if (!formatPattern.matcher(tag.getFirstArg()).find()) { log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern()); } } } if (tagCount == 0) { log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName); } } } /** * Verifies that a type definition has the specified param tag for * the specified type parameter name. * @param lineNo the line number for the type definition. * @param tags tags from the Javadoc comment for the type definition. * @param typeParamName the name of the type parameter */ private void checkTypeParamTag(final int lineNo, final List<JavadocTag> tags, final String typeParamName) { boolean found = false; for (int i = tags.size() - 1; i >= 0; i--) { final JavadocTag tag = tags.get(i); if (tag.isParamTag() && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET) == 0) { found = true; break; } } if (!found) { log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText() + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); } } /** * Checks for unused param tags for type parameters. * @param tags tags from the Javadoc comment for the type definition. * @param typeParamNames names of type parameters */ private void checkUnusedTypeParamTags( final List<JavadocTag> tags, final List<String> typeParamNames) { for (int i = tags.size() - 1; i >= 0; i--) { final JavadocTag tag = tags.get(i); if (tag.isParamTag()) { final String typeParamName = extractTypeParamNameFromTag(tag); if (!typeParamNames.contains(typeParamName)) { log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG, JavadocTagInfo.PARAM.getText(), OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET); } } } } /** * Extracts type parameter name from tag. * @param tag javadoc tag to extract parameter name * @return extracts type parameter name from tag */ private static String extractTypeParamNameFromTag(JavadocTag tag) { final String typeParamName; final Matcher matchInAngleBrackets = TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg()); if (matchInAngleBrackets.find()) { typeParamName = matchInAngleBrackets.group(1).trim(); } else { typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0]; } return typeParamName; } }