blob: 4ec74b1b8bfaea6d63cafb3dc28be9ade093688a [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
// 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>
* &lt;module name="JavadocDeprecated"/&gt;
* </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> &lt;property name="skipNoJavadoc" value="true" /&gt;</pre>
*
* <p>Examples of validating source code with skipNoJavadoc:</p>
*
* <pre>
* <code>
* {@literal @}deprecated
* public static final int MY_CONST = 123456; // no violation
*
* &#47;** This javadoc is missing deprecated tag. *&#47;
* {@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;
}
}