| //////////////////////////////////////////////////////////////////////////////// |
| // 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.annotation; |
| |
| 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.TextBlock; |
| import com.puppycrawl.tools.checkstyle.api.TokenTypes; |
| import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; |
| import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility; |
| import com.puppycrawl.tools.checkstyle.utils.CommonUtils; |
| |
| /** |
| * <p> |
| * This class is used to verify that both the |
| * {@link Deprecated Deprecated} annotation |
| * and the deprecated javadoc tag are present when |
| * either one is present. |
| * </p> |
| * |
| * <p> |
| * Both ways of flagging deprecation serve their own purpose. The |
| * {@link Deprecated Deprecated} annotation is used for |
| * compilers and development tools. The deprecated javadoc tag is |
| * used to document why something is deprecated and what, if any, |
| * alternatives exist. |
| * </p> |
| * |
| * <p> |
| * In order to properly mark something as deprecated both forms of |
| * deprecation should be present. |
| * </p> |
| * |
| * <p> |
| * Package deprecation is a exception to the rule of always using the |
| * javadoc tag and annotation to deprecate. Only the package-info.java |
| * file can contain a Deprecated annotation and it CANNOT contain |
| * a deprecated javadoc tag. This is the case with |
| * Sun's javadoc tool released with JDK 1.6.0_11. As a result, this check |
| * does not deal with Deprecated packages in any way. <b>No official |
| * documentation was found confirming this behavior is correct |
| * (of the javadoc tool).</b> |
| * </p> |
| * |
| * <p> |
| * To configure this check do the following: |
| * </p> |
| * |
| * <pre> |
| * <module name="JavadocDeprecated"/> |
| * </pre> |
| * |
| * <p> |
| * In addition you can configure this check with skipNoJavadoc |
| * option to allow it to ignore cases when JavaDoc is missing, |
| * but still warns when JavaDoc is present but either |
| * {@link Deprecated Deprecated} is missing from JavaDoc or |
| * {@link Deprecated Deprecated} is missing from the element. |
| * To configure this check to allow it use: |
| * </p> |
| * |
| * <pre> <property name="skipNoJavadoc" value="true" /></pre> |
| * |
| * <p>Examples of validating source code with skipNoJavadoc:</p> |
| * |
| * <pre> |
| * <code> |
| * {@literal @}deprecated |
| * public static final int MY_CONST = 123456; // no violation |
| * |
| * /** This javadoc is missing deprecated tag. */ |
| * {@literal @}deprecated |
| * public static final int COUNTER = 10; // violation as javadoc exists |
| * </code> |
| * </pre> |
| * |
| * @author Travis Schneeberger |
| */ |
| @StatelessCheck |
| public final class MissingDeprecatedCheck extends AbstractCheck { |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_ANNOTATION_MISSING_DEPRECATED = |
| "annotation.missing.deprecated"; |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_JAVADOC_DUPLICATE_TAG = |
| "javadoc.duplicateTag"; |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing"; |
| |
| /** {@link Deprecated Deprecated} annotation name. */ |
| private static final String DEPRECATED = "Deprecated"; |
| |
| /** Fully-qualified {@link Deprecated Deprecated} annotation name. */ |
| private static final String FQ_DEPRECATED = "java.lang." + DEPRECATED; |
| |
| /** Compiled regexp to match Javadoc tag with no argument. */ |
| private static final Pattern MATCH_DEPRECATED = |
| CommonUtils.createPattern("@(deprecated)\\s+\\S"); |
| |
| /** Compiled regexp to match first part of multilineJavadoc tags. */ |
| private static final Pattern MATCH_DEPRECATED_MULTILINE_START = |
| CommonUtils.createPattern("@(deprecated)\\s*$"); |
| |
| /** Compiled regexp to look for a continuation of the comment. */ |
| private static final Pattern MATCH_DEPRECATED_MULTILINE_CONT = |
| CommonUtils.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 = "@"; |
| |
| /** Is deprecated element valid without javadoc. */ |
| private boolean skipNoJavadoc; |
| |
| /** |
| * Set skipJavadoc value. |
| * @param skipNoJavadoc user's value of skipJavadoc |
| */ |
| public void setSkipNoJavadoc(boolean skipNoJavadoc) { |
| this.skipNoJavadoc = skipNoJavadoc; |
| } |
| |
| @Override |
| public int[] getDefaultTokens() { |
| return getAcceptableTokens(); |
| } |
| |
| @Override |
| public int[] getAcceptableTokens() { |
| return new int[] { |
| TokenTypes.INTERFACE_DEF, |
| TokenTypes.CLASS_DEF, |
| TokenTypes.ANNOTATION_DEF, |
| TokenTypes.ENUM_DEF, |
| TokenTypes.METHOD_DEF, |
| TokenTypes.CTOR_DEF, |
| TokenTypes.VARIABLE_DEF, |
| TokenTypes.ENUM_CONSTANT_DEF, |
| TokenTypes.ANNOTATION_FIELD_DEF, |
| }; |
| } |
| |
| @Override |
| public int[] getRequiredTokens() { |
| return getAcceptableTokens(); |
| } |
| |
| @Override |
| public void visitToken(final DetailAST ast) { |
| final TextBlock javadoc = |
| getFileContents().getJavadocBefore(ast.getLineNo()); |
| |
| final boolean containsAnnotation = |
| AnnotationUtility.containsAnnotation(ast, DEPRECATED) |
| || AnnotationUtility.containsAnnotation(ast, FQ_DEPRECATED); |
| |
| final boolean containsJavadocTag = containsJavadocTag(javadoc); |
| |
| if (containsAnnotation ^ containsJavadocTag && !(skipNoJavadoc && javadoc == null)) { |
| log(ast.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED); |
| } |
| } |
| |
| /** |
| * Checks to see if the text block contains a deprecated tag. |
| * |
| * @param javadoc the javadoc of the AST |
| * @return true if contains the tag |
| */ |
| private boolean containsJavadocTag(final TextBlock javadoc) { |
| boolean found = false; |
| if (javadoc != null) { |
| final String[] lines = javadoc.getText(); |
| int currentLine = javadoc.getStartLineNo() - 1; |
| |
| for (int i = 0; i < lines.length; i++) { |
| currentLine++; |
| final String line = lines[i]; |
| |
| final Matcher javadocNoArgMatcher = MATCH_DEPRECATED.matcher(line); |
| final Matcher noArgMultilineStart = MATCH_DEPRECATED_MULTILINE_START.matcher(line); |
| |
| if (javadocNoArgMatcher.find()) { |
| if (found) { |
| log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, |
| JavadocTagInfo.DEPRECATED.getText()); |
| } |
| found = true; |
| } |
| else if (noArgMultilineStart.find()) { |
| found = checkTagAtTheRestOfComment(lines, found, currentLine, i); |
| } |
| } |
| } |
| return found; |
| } |
| |
| /** |
| * Look for the rest of the comment if all we saw was |
| * the tag and the name. Stop when we see '*' (end of |
| * Javadoc), '{@literal @}' (start of next tag), or anything that's |
| * not whitespace or '*' characters. |
| * @param lines all lines |
| * @param foundBefore flag from parent method |
| * @param currentLine current line |
| * @param index som index |
| * @return true if Tag is found |
| */ |
| private boolean checkTagAtTheRestOfComment(String[] lines, boolean foundBefore, |
| int currentLine, int index) { |
| |
| boolean found = false; |
| int reindex = index + 1; |
| while (reindex <= lines.length - 1) { |
| final Matcher multilineCont = MATCH_DEPRECATED_MULTILINE_CONT.matcher(lines[reindex]); |
| |
| if (multilineCont.find()) { |
| reindex = lines.length; |
| final String lFin = multilineCont.group(1); |
| if (lFin.equals(NEXT_TAG) || lFin.equals(END_JAVADOC)) { |
| log(currentLine, MSG_KEY_JAVADOC_MISSING); |
| if (foundBefore) { |
| log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, |
| JavadocTagInfo.DEPRECATED.getText()); |
| } |
| found = true; |
| } |
| else { |
| if (foundBefore) { |
| log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, |
| JavadocTagInfo.DEPRECATED.getText()); |
| } |
| found = true; |
| } |
| } |
| reindex++; |
| } |
| return found; |
| } |
| } |