All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck Maven / Gradle / Ivy

Go to download

Checkstyle is a development tool to help programmers write Java code that adheres to a coding standard

The newest version!
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2024 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.puppycrawl.tools.checkstyle.StatelessCheck;
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.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TextBlock;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;

/**
 * 
* Checks the Javadoc of a method or constructor. *
* *

* Violates parameters and type parameters for which no param tags are present can * be suppressed by defining property {@code allowMissingParamTags}. *

* *

* Violates methods which return non-void but for which no return tag is present can * be suppressed by defining property {@code allowMissingReturnTag}. *

* *

* Violates exceptions which are declared to be thrown (by {@code throws} in the method * signature or by {@code throw new} in the method body), but for which no throws tag is * present by activation of property {@code validateThrows}. * Note that {@code throw new} is not checked in the following places: *

*
    *
  • * Inside a try block (with catch). It is not possible to determine if the thrown * exception can be caught by the catch block as there is no knowledge of the * inheritance hierarchy, so the try block is ignored entirely. However, catch * and finally blocks, as well as try blocks without catch, are still checked. *
  • *
  • * Local classes, anonymous classes and lambda expressions. It is not known when the * throw statements inside such classes are going to be evaluated, so they are ignored. *
  • *
* *

* ATTENTION: Checkstyle does not have information about hierarchy of exception types * so usage of base class is considered as separate exception type. * As workaround, you need to specify both types in javadoc (parent and exact type). *

* *

* Javadoc is not required on a method that is tagged with the {@code @Override} * annotation. However, under Java 5 it is not possible to mark a method required * for an interface (this was corrected under Java 6). Hence, Checkstyle * supports using the convention of using a single {@code {@inheritDoc}} tag * instead of all the other tags. *

* *

* Note that only inheritable items will allow the {@code {@inheritDoc}} * tag to be used in place of comments. Static methods at all visibilities, * private non-static methods and constructors are not inheritable. *

* *

* For example, if the following method is implementing a method required by * an interface, then the Javadoc could be done as: *

*
 * /** {@inheritDoc} */
 * public int checkReturnTag(final int aTagIndex,
 *                           JavadocTag[] aTags,
 *                           int aLineNo)
 * 
*
    *
  • * Property {@code accessModifiers} - Specify the access modifiers where Javadoc comments are * checked. * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}. * Default value is {@code public, protected, package, private}. *
  • *
  • * Property {@code allowMissingParamTags} - Control whether to ignore violations * when a method has parameters but does not have matching {@code param} tags in the javadoc. * Type is {@code boolean}. * Default value is {@code false}. *
  • *
  • * Property {@code allowMissingReturnTag} - Control whether to ignore violations * when a method returns non-void type and does not have a {@code return} tag in the javadoc. * Type is {@code boolean}. * Default value is {@code false}. *
  • *
  • * Property {@code allowedAnnotations} - Specify annotations that allow missed documentation. * Type is {@code java.lang.String[]}. * Default value is {@code Override}. *
  • *
  • * Property {@code validateThrows} - Control whether to validate {@code throws} tags. * Type is {@code boolean}. * Default value is {@code false}. *
  • *
  • * Property {@code tokens} - tokens to check * Type is {@code java.lang.String[]}. * Validation type is {@code tokenSet}. * Default value is: * * METHOD_DEF, * * CTOR_DEF, * * ANNOTATION_FIELD_DEF, * * COMPACT_CTOR_DEF. *
  • *
* *

* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} *

* *

* Violation Message Keys: *

*
    *
  • * {@code javadoc.classInfo} *
  • *
  • * {@code javadoc.duplicateTag} *
  • *
  • * {@code javadoc.expectedTag} *
  • *
  • * {@code javadoc.invalidInheritDoc} *
  • *
  • * {@code javadoc.return.expected} *
  • *
  • * {@code javadoc.unusedTag} *
  • *
  • * {@code javadoc.unusedTagGeneral} *
  • *
* * @since 3.0 */ @StatelessCheck public class JavadocMethodCheck extends AbstractCheck { /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_CLASS_INFO = "javadoc.classInfo"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_INVALID_INHERIT_DOC = "javadoc.invalidInheritDoc"; /** * 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_EXPECTED_TAG = "javadoc.expectedTag"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_RETURN_EXPECTED = "javadoc.return.expected"; /** * A key is pointing to the warning message text in "messages.properties" * file. */ public static final String MSG_DUPLICATE_TAG = "javadoc.duplicateTag"; /** Html element start symbol. */ private static final String ELEMENT_START = "<"; /** Html element end symbol. */ private static final String ELEMENT_END = ">"; /** Compiled regexp to match Javadoc tags that take an argument. */ private static final Pattern MATCH_JAVADOC_ARG = CommonUtil.createPattern( "^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+(\\S+)\\s+\\S*"); /** Compiled regexp to match Javadoc tags with argument but with missing description. */ private static final Pattern MATCH_JAVADOC_ARG_MISSING_DESCRIPTION = CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(throws|exception|param)\\s+" + "(\\S[^*]*)(?:(\\s+|\\*\\/))?"); /** Compiled regexp to look for a continuation of the comment. */ private static final Pattern MATCH_JAVADOC_MULTILINE_CONT = CommonUtil.createPattern("(\\*\\/|@|[^\\s\\*])"); /** Multiline finished at end of comment. */ private static final String END_JAVADOC = "*/"; /** Multiline finished at next Javadoc. */ private static final String NEXT_TAG = "@"; /** Compiled regexp to match Javadoc tags with no argument. */ private static final Pattern MATCH_JAVADOC_NOARG = CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s+\\S"); /** Compiled regexp to match first part of multilineJavadoc tags. */ private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START = CommonUtil.createPattern("^\\s*(?>\\*|\\/\\*\\*)?\\s*@(return|see)\\s*$"); /** Compiled regexp to match Javadoc tags with no argument and {}. */ private static final Pattern MATCH_JAVADOC_NOARG_CURLY = CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}"); /** Specify the access modifiers where Javadoc comments are checked. */ private AccessModifierOption[] accessModifiers = { AccessModifierOption.PUBLIC, AccessModifierOption.PROTECTED, AccessModifierOption.PACKAGE, AccessModifierOption.PRIVATE, }; /** * Control whether to validate {@code throws} tags. */ private boolean validateThrows; /** * Control whether to ignore violations when a method has parameters but does * not have matching {@code param} tags in the javadoc. */ private boolean allowMissingParamTags; /** * Control whether to ignore violations when a method returns non-void type * and does not have a {@code return} tag in the javadoc. */ private boolean allowMissingReturnTag; /** Specify annotations that allow missed documentation. */ private Set allowedAnnotations = Set.of("Override"); /** * Setter to control whether to validate {@code throws} tags. * * @param value user's value. * @since 6.0 */ public void setValidateThrows(boolean value) { validateThrows = value; } /** * Setter to specify annotations that allow missed documentation. * * @param userAnnotations user's value. * @since 6.0 */ public void setAllowedAnnotations(String... userAnnotations) { allowedAnnotations = Set.of(userAnnotations); } /** * Setter to specify the access modifiers where Javadoc comments are checked. * * @param accessModifiers access modifiers. * @since 8.42 */ public void setAccessModifiers(AccessModifierOption... accessModifiers) { this.accessModifiers = UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length); } /** * Setter to control whether to ignore violations when a method has parameters * but does not have matching {@code param} tags in the javadoc. * * @param flag a {@code Boolean} value * @since 3.1 */ public void setAllowMissingParamTags(boolean flag) { allowMissingParamTags = flag; } /** * Setter to control whether to ignore violations when a method returns non-void type * and does not have a {@code return} tag in the javadoc. * * @param flag a {@code Boolean} value * @since 3.1 */ public void setAllowMissingReturnTag(boolean flag) { allowMissingReturnTag = flag; } @Override public final int[] getRequiredTokens() { return CommonUtil.EMPTY_INT_ARRAY; } @Override public int[] getDefaultTokens() { return getAcceptableTokens(); } @Override public int[] getAcceptableTokens() { return new int[] { TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, TokenTypes.ANNOTATION_FIELD_DEF, TokenTypes.COMPACT_CTOR_DEF, }; } @Override public final void visitToken(DetailAST ast) { processAST(ast); } /** * Called to process an AST when visiting it. * * @param ast the AST to process. Guaranteed to not be PACKAGE_DEF or * IMPORT tokens. */ // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 @SuppressWarnings("deprecation") private void processAST(DetailAST ast) { if (shouldCheck(ast)) { final FileContents contents = getFileContents(); final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo()); if (textBlock != null) { checkComment(ast, textBlock); } } } /** * 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 AccessModifierOption surroundingAccessModifier = CheckUtil .getSurroundingAccessModifier(ast); final AccessModifierOption accessModifier = CheckUtil .getAccessModifierFromModifiersToken(ast); return surroundingAccessModifier != null && Arrays.stream(accessModifiers) .anyMatch(modifier -> modifier == surroundingAccessModifier) && Arrays.stream(accessModifiers).anyMatch(modifier -> modifier == accessModifier); } /** * Checks the Javadoc for a method. * * @param ast the token for the method * @param comment the Javadoc comment */ private void checkComment(DetailAST ast, TextBlock comment) { final List tags = getMethodTags(comment); if (!hasShortCircuitTag(ast, tags)) { if (ast.getType() == TokenTypes.ANNOTATION_FIELD_DEF) { checkReturnTag(tags, ast.getLineNo(), true); } else { final Iterator it = tags.iterator(); // Check for inheritDoc boolean hasInheritDocTag = false; while (!hasInheritDocTag && it.hasNext()) { hasInheritDocTag = it.next().isInheritDocTag(); } final boolean reportExpectedTags = !hasInheritDocTag && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations); // COMPACT_CTOR_DEF has no parameters if (ast.getType() != TokenTypes.COMPACT_CTOR_DEF) { checkParamTags(tags, ast, reportExpectedTags); } final List throwed = combineExceptionInfo(getThrows(ast), getThrowed(ast)); checkThrowsTags(tags, throwed, reportExpectedTags); if (CheckUtil.isNonVoidMethod(ast)) { checkReturnTag(tags, ast.getLineNo(), reportExpectedTags); } } // Dump out all unused tags tags.stream().filter(javadocTag -> !javadocTag.isSeeOrInheritDocTag()) .forEach(javadocTag -> log(javadocTag.getLineNo(), MSG_UNUSED_TAG_GENERAL)); } } /** * Validates whether the Javadoc has a short circuit tag. Currently, this is * the inheritTag. Any violations are logged. * * @param ast the construct being checked * @param tags the list of Javadoc tags associated with the construct * @return true if the construct has a short circuit tag. */ private boolean hasShortCircuitTag(final DetailAST ast, final List tags) { boolean result = true; // Check if it contains {@inheritDoc} tag if (tags.size() == 1 && tags.get(0).isInheritDocTag()) { // Invalid if private, a constructor, or a static method if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) { log(ast, MSG_INVALID_INHERIT_DOC); } } else { result = false; } return result; } /** * Returns the tags in a javadoc comment. Only finds throws, exception, * param, return and see tags. * * @param comment the Javadoc comment * @return the tags found */ private static List getMethodTags(TextBlock comment) { final String[] lines = comment.getText(); final List tags = new ArrayList<>(); int currentLine = comment.getStartLineNo() - 1; final int startColumnNumber = comment.getStartColNo(); for (int i = 0; i < lines.length; i++) { currentLine++; final Matcher javadocArgMatcher = MATCH_JAVADOC_ARG.matcher(lines[i]); final Matcher javadocArgMissingDescriptionMatcher = MATCH_JAVADOC_ARG_MISSING_DESCRIPTION.matcher(lines[i]); final Matcher javadocNoargMatcher = MATCH_JAVADOC_NOARG.matcher(lines[i]); final Matcher noargCurlyMatcher = MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]); final Matcher noargMultilineStart = MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]); if (javadocArgMatcher.find()) { final int col = calculateTagColumn(javadocArgMatcher, i, startColumnNumber); tags.add(new JavadocTag(currentLine, col, javadocArgMatcher.group(1), javadocArgMatcher.group(2))); } else if (javadocArgMissingDescriptionMatcher.find()) { final int col = calculateTagColumn(javadocArgMissingDescriptionMatcher, i, startColumnNumber); tags.add(new JavadocTag(currentLine, col, javadocArgMissingDescriptionMatcher.group(1), javadocArgMissingDescriptionMatcher.group(2))); } else if (javadocNoargMatcher.find()) { final int col = calculateTagColumn(javadocNoargMatcher, i, startColumnNumber); tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher.group(1))); } else if (noargCurlyMatcher.find()) { tags.add(new JavadocTag(currentLine, 0, noargCurlyMatcher.group(1))); } else if (noargMultilineStart.find()) { tags.addAll(getMultilineNoArgTags(noargMultilineStart, lines, i, currentLine)); } } return tags; } /** * Calculates column number using Javadoc tag matcher. * * @param javadocTagMatchResult found javadoc tag match result * @param lineNumber line number of Javadoc tag in comment * @param startColumnNumber column number of Javadoc comment beginning * @return column number */ private static int calculateTagColumn(MatchResult javadocTagMatchResult, int lineNumber, int startColumnNumber) { int col = javadocTagMatchResult.start(1) - 1; if (lineNumber == 0) { col += startColumnNumber; } return col; } /** * Gets multiline Javadoc tags with no arguments. * * @param noargMultilineStart javadoc tag Matcher * @param lines comment text lines * @param lineIndex line number that contains the javadoc tag * @param tagLine javadoc tag line number in file * @return javadoc tags with no arguments */ private static List getMultilineNoArgTags(final Matcher noargMultilineStart, final String[] lines, final int lineIndex, final int tagLine) { int remIndex = lineIndex; Matcher multilineCont; do { remIndex++; multilineCont = MATCH_JAVADOC_MULTILINE_CONT.matcher(lines[remIndex]); } while (!multilineCont.find()); final List tags = new ArrayList<>(); final String lFin = multilineCont.group(1); if (!NEXT_TAG.equals(lFin) && !END_JAVADOC.equals(lFin)) { final String param1 = noargMultilineStart.group(1); final int col = noargMultilineStart.start(1) - 1; tags.add(new JavadocTag(tagLine, col, param1)); } return tags; } /** * Computes the parameter nodes for a method. * * @param ast the method node. * @return the list of parameter nodes for ast. */ private static List getParameters(DetailAST ast) { final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); final List returnValue = new ArrayList<>(); DetailAST child = params.getFirstChild(); while (child != null) { final DetailAST ident = child.findFirstToken(TokenTypes.IDENT); if (ident != null) { returnValue.add(ident); } child = child.getNextSibling(); } return returnValue; } /** * Computes the exception nodes for a method. * * @param ast the method node. * @return the list of exception nodes for ast. */ private static List getThrows(DetailAST ast) { final List returnValue = new ArrayList<>(); final DetailAST throwsAST = ast .findFirstToken(TokenTypes.LITERAL_THROWS); if (throwsAST != null) { DetailAST child = throwsAST.getFirstChild(); while (child != null) { if (child.getType() == TokenTypes.IDENT || child.getType() == TokenTypes.DOT) { returnValue.add(getExceptionInfo(child)); } child = child.getNextSibling(); } } return returnValue; } /** * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'. * * @param methodAst method DetailAST object where to find exceptions * @return list of ExceptionInfo */ private static List getThrowed(DetailAST methodAst) { final List returnValue = new ArrayList<>(); final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST); if (blockAst != null) { final List throwLiterals = findTokensInAstByType(blockAst, TokenTypes.LITERAL_THROW); for (DetailAST throwAst : throwLiterals) { if (!isInIgnoreBlock(blockAst, throwAst)) { final DetailAST newAst = throwAst.getFirstChild().getFirstChild(); if (newAst.getType() == TokenTypes.LITERAL_NEW) { final DetailAST child = newAst.getFirstChild(); returnValue.add(getExceptionInfo(child)); } } } } return returnValue; } /** * Get ExceptionInfo instance. * * @param ast DetailAST object where to find exceptions node; * @return ExceptionInfo */ private static ExceptionInfo getExceptionInfo(DetailAST ast) { final FullIdent ident = FullIdent.createFullIdent(ast); final DetailAST firstClassNameNode = getFirstClassNameNode(ast); return new ExceptionInfo(firstClassNameNode, new ClassInfo(new Token(ident))); } /** * Get node where class name of exception starts. * * @param ast DetailAST object where to find exceptions node; * @return exception node where class name starts */ private static DetailAST getFirstClassNameNode(DetailAST ast) { DetailAST startNode = ast; while (startNode.getType() == TokenTypes.DOT) { startNode = startNode.getFirstChild(); } return startNode; } /** * Checks if a 'throw' usage is contained within a block that should be ignored. * Such blocks consist of try (with catch) blocks, local classes, anonymous classes, * and lambda expressions. Note that a try block without catch is not considered. * * @param methodBodyAst DetailAST node representing the method body * @param throwAst DetailAST node representing the 'throw' literal * @return true if throwAst is inside a block that should be ignored */ private static boolean isInIgnoreBlock(DetailAST methodBodyAst, DetailAST throwAst) { DetailAST ancestor = throwAst; while (ancestor != methodBodyAst) { if (ancestor.getType() == TokenTypes.LAMBDA || ancestor.getType() == TokenTypes.OBJBLOCK || ancestor.findFirstToken(TokenTypes.LITERAL_CATCH) != null) { // throw is inside a lambda expression/anonymous class/local class, // or throw is inside a try block, and there is a catch block break; } if (ancestor.getType() == TokenTypes.LITERAL_CATCH || ancestor.getType() == TokenTypes.LITERAL_FINALLY) { // if the throw is inside a catch or finally block, // skip the immediate ancestor (try token) ancestor = ancestor.getParent(); } ancestor = ancestor.getParent(); } return ancestor != methodBodyAst; } /** * Combine ExceptionInfo collections together by matching names. * * @param first the first collection of ExceptionInfo * @param second the second collection of ExceptionInfo * @return combined list of ExceptionInfo */ private static List combineExceptionInfo(Collection first, Iterable second) { final List result = new ArrayList<>(first); for (ExceptionInfo exceptionInfo : second) { if (result.stream().noneMatch(item -> isExceptionInfoSame(item, exceptionInfo))) { result.add(exceptionInfo); } } return result; } /** * Finds node of specified type among root children, siblings, siblings children * on any deep level. * * @param root DetailAST * @param astType value of TokenType * @return {@link List} of {@link DetailAST} nodes which matches the predicate. */ public static List findTokensInAstByType(DetailAST root, int astType) { final List result = new ArrayList<>(); // iterative preorder depth-first search DetailAST curNode = root; do { // process curNode if (curNode.getType() == astType) { result.add(curNode); } // process children (if any) if (curNode.hasChildren()) { curNode = curNode.getFirstChild(); continue; } // backtrack to parent if last child, stopping at root while (curNode != root && curNode.getNextSibling() == null) { curNode = curNode.getParent(); } // explore siblings if not root if (curNode != root) { curNode = curNode.getNextSibling(); } } while (curNode != root); return result; } /** * Checks a set of tags for matching parameters. * * @param tags the tags to check * @param parent the node which takes the parameters * @param reportExpectedTags whether we should report if do not find * expected tag */ private void checkParamTags(final List tags, final DetailAST parent, boolean reportExpectedTags) { final List params = getParameters(parent); final List typeParams = CheckUtil .getTypeParameters(parent); // Loop over the tags, checking to see they exist in the params. final ListIterator tagIt = tags.listIterator(); while (tagIt.hasNext()) { final JavadocTag tag = tagIt.next(); if (!tag.isParamTag()) { continue; } tagIt.remove(); final String arg1 = tag.getFirstArg(); boolean found = removeMatchingParam(params, arg1); if (arg1.startsWith(ELEMENT_START) && arg1.endsWith(ELEMENT_END)) { found = searchMatchingTypeParameter(typeParams, arg1.substring(1, arg1.length() - 1)); } // Handle extra JavadocTag if (!found) { log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG, "@param", arg1); } } // Now dump out all type parameters/parameters without tags :- unless // the user has chosen to suppress these problems if (!allowMissingParamTags && reportExpectedTags) { for (DetailAST param : params) { log(param, MSG_EXPECTED_TAG, JavadocTagInfo.PARAM.getText(), param.getText()); } for (DetailAST typeParam : typeParams) { log(typeParam, MSG_EXPECTED_TAG, JavadocTagInfo.PARAM.getText(), ELEMENT_START + typeParam.findFirstToken(TokenTypes.IDENT).getText() + ELEMENT_END); } } } /** * Returns true if required type found in type parameters. * * @param typeParams * collection of type parameters * @param requiredTypeName * name of required type * @return true if required type found in type parameters. */ private static boolean searchMatchingTypeParameter(Iterable typeParams, String requiredTypeName) { // Loop looking for matching type param final Iterator typeParamsIt = typeParams.iterator(); boolean found = false; while (typeParamsIt.hasNext()) { final DetailAST typeParam = typeParamsIt.next(); if (typeParam.findFirstToken(TokenTypes.IDENT).getText() .equals(requiredTypeName)) { found = true; typeParamsIt.remove(); break; } } return found; } /** * Remove parameter from params collection by name. * * @param params collection of DetailAST parameters * @param paramName name of parameter * @return true if parameter found and removed */ private static boolean removeMatchingParam(Iterable params, String paramName) { boolean found = false; final Iterator paramIt = params.iterator(); while (paramIt.hasNext()) { final DetailAST param = paramIt.next(); if (param.getText().equals(paramName)) { found = true; paramIt.remove(); break; } } return found; } /** * Checks for only one return tag. All return tags will be removed from the * supplied list. * * @param tags the tags to check * @param lineNo the line number of the expected tag * @param reportExpectedTags whether we should report if do not find * expected tag */ private void checkReturnTag(List tags, int lineNo, boolean reportExpectedTags) { // Loop over tags finding return tags. After the first one, report a // violation. boolean found = false; final ListIterator it = tags.listIterator(); while (it.hasNext()) { final JavadocTag javadocTag = it.next(); if (javadocTag.isReturnTag()) { if (found) { log(javadocTag.getLineNo(), javadocTag.getColumnNo(), MSG_DUPLICATE_TAG, JavadocTagInfo.RETURN.getText()); } found = true; it.remove(); } } // Handle there being no @return tags :- unless // the user has chosen to suppress these problems if (!found && !allowMissingReturnTag && reportExpectedTags) { log(lineNo, MSG_RETURN_EXPECTED); } } /** * Checks a set of tags for matching throws. * * @param tags the tags to check * @param throwsList the throws to check * @param reportExpectedTags whether we should report if do not find * expected tag */ private void checkThrowsTags(List tags, List throwsList, boolean reportExpectedTags) { // Loop over the tags, checking to see they exist in the throws. final ListIterator tagIt = tags.listIterator(); while (tagIt.hasNext()) { final JavadocTag tag = tagIt.next(); if (!tag.isThrowsTag()) { continue; } tagIt.remove(); // Loop looking for matching throw processThrows(throwsList, tag.getFirstArg()); } // Now dump out all throws without tags :- unless // the user has chosen to suppress these problems if (validateThrows && reportExpectedTags) { throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound()) .forEach(exceptionInfo -> { final Token token = exceptionInfo.getName(); log(exceptionInfo.getAst(), MSG_EXPECTED_TAG, JavadocTagInfo.THROWS.getText(), token.getText()); }); } } /** * Verifies that documented exception is in throws. * * @param throwsIterable collection of throws * @param documentedClassName documented exception class name */ private static void processThrows(Iterable throwsIterable, String documentedClassName) { for (ExceptionInfo exceptionInfo : throwsIterable) { if (isClassNamesSame(exceptionInfo.getName().getText(), documentedClassName)) { exceptionInfo.setFound(); break; } } } /** * Check that ExceptionInfo objects are same by name. * * @param info1 ExceptionInfo object * @param info2 ExceptionInfo object * @return true is ExceptionInfo object have the same name */ private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) { return isClassNamesSame(info1.getName().getText(), info2.getName().getText()); } /** * Check that class names are same by short name of class. If some class name is fully * qualified it is cut to short name. * * @param class1 class name * @param class2 class name * @return true is ExceptionInfo object have the same name */ private static boolean isClassNamesSame(String class1, String class2) { boolean result = false; if (class1.equals(class2)) { result = true; } else { final String separator = "."; if (class1.contains(separator) || class2.contains(separator)) { final String class1ShortName = class1 .substring(class1.lastIndexOf('.') + 1); final String class2ShortName = class2 .substring(class2.lastIndexOf('.') + 1); result = class1ShortName.equals(class2ShortName); } } return result; } /** * Contains class's {@code Token}. */ private static class ClassInfo { /** {@code FullIdent} associated with this class. */ private final Token name; /** * Creates new instance of class information object. * * @param className token which represents class name. * @throws IllegalArgumentException when className is nulls */ protected ClassInfo(final Token className) { name = className; } /** * Gets class name. * * @return class name */ public final Token getName() { return name; } } /** * Represents text element with location in the text. */ private static final class Token { /** Token's column number. */ private final int columnNo; /** Token's line number. */ private final int lineNo; /** Token's text. */ private final String text; /** * Creates token. * * @param text token's text * @param lineNo token's line number * @param columnNo token's column number */ private Token(String text, int lineNo, int columnNo) { this.text = text; this.lineNo = lineNo; this.columnNo = columnNo; } /** * Converts FullIdent to Token. * * @param fullIdent full ident to convert. */ private Token(FullIdent fullIdent) { text = fullIdent.getText(); lineNo = fullIdent.getLineNo(); columnNo = fullIdent.getColumnNo(); } /** * Gets text of the token. * * @return text of the token */ public String getText() { return text; } @Override public String toString() { return "Token[" + text + "(" + lineNo + "x" + columnNo + ")]"; } } /** Stores useful information about declared exception. */ private static final class ExceptionInfo { /** AST node representing this exception. */ private final DetailAST ast; /** Class information associated with this exception. */ private final ClassInfo classInfo; /** Does the exception have throws tag associated with. */ private boolean found; /** * Creates new instance for {@code FullIdent}. * * @param ast AST node representing this exception * @param classInfo class info */ private ExceptionInfo(DetailAST ast, ClassInfo classInfo) { this.ast = ast; this.classInfo = classInfo; } /** * Gets the AST node representing this exception. * * @return the AST node representing this exception */ private DetailAST getAst() { return ast; } /** Mark that the exception has associated throws tag. */ private void setFound() { found = true; } /** * Checks that the exception has throws tag associated with it. * * @return whether the exception has throws tag associated with */ private boolean isFound() { return found; } /** * Gets exception name. * * @return exception's name */ private Token getName() { return classInfo.getName(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy