001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.annotation; 021 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TextBlock; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 030import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 032 033/** 034 * <p> 035 * This class is used to verify that both the 036 * {@link Deprecated Deprecated} annotation 037 * and the deprecated javadoc tag are present when 038 * either one is present. 039 * </p> 040 * 041 * <p> 042 * Both ways of flagging deprecation serve their own purpose. The 043 * {@link Deprecated Deprecated} annotation is used for 044 * compilers and development tools. The deprecated javadoc tag is 045 * used to document why something is deprecated and what, if any, 046 * alternatives exist. 047 * </p> 048 * 049 * <p> 050 * In order to properly mark something as deprecated both forms of 051 * deprecation should be present. 052 * </p> 053 * 054 * <p> 055 * Package deprecation is a exception to the rule of always using the 056 * javadoc tag and annotation to deprecate. Only the package-info.java 057 * file can contain a Deprecated annotation and it CANNOT contain 058 * a deprecated javadoc tag. This is the case with 059 * Sun's javadoc tool released with JDK 1.6.0_11. As a result, this check 060 * does not deal with Deprecated packages in any way. <b>No official 061 * documentation was found confirming this behavior is correct 062 * (of the javadoc tool).</b> 063 * </p> 064 * 065 * <p> 066 * To configure this check do the following: 067 * </p> 068 * 069 * <pre> 070 * <module name="JavadocDeprecated"/> 071 * </pre> 072 * 073 * <p> 074 * In addition you can configure this check with skipNoJavadoc 075 * option to allow it to ignore cases when JavaDoc is missing, 076 * but still warns when JavaDoc is present but either 077 * {@link Deprecated Deprecated} is missing from JavaDoc or 078 * {@link Deprecated Deprecated} is missing from the element. 079 * To configure this check to allow it use: 080 * </p> 081 * 082 * <pre> <property name="skipNoJavadoc" value="true" /></pre> 083 * 084 * <p>Examples of validating source code with skipNoJavadoc:</p> 085 * 086 * <pre> 087 * <code> 088 * {@literal @}deprecated 089 * public static final int MY_CONST = 123456; // no violation 090 * 091 * /** This javadoc is missing deprecated tag. */ 092 * {@literal @}deprecated 093 * public static final int COUNTER = 10; // violation as javadoc exists 094 * </code> 095 * </pre> 096 * 097 * @author Travis Schneeberger 098 */ 099public final class MissingDeprecatedCheck extends AbstractCheck { 100 /** 101 * A key is pointing to the warning message text in "messages.properties" 102 * file. 103 */ 104 public static final String MSG_KEY_ANNOTATION_MISSING_DEPRECATED = 105 "annotation.missing.deprecated"; 106 107 /** 108 * A key is pointing to the warning message text in "messages.properties" 109 * file. 110 */ 111 public static final String MSG_KEY_JAVADOC_DUPLICATE_TAG = 112 "javadoc.duplicateTag"; 113 114 /** 115 * A key is pointing to the warning message text in "messages.properties" 116 * file. 117 */ 118 public static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing"; 119 120 /** {@link Deprecated Deprecated} annotation name. */ 121 private static final String DEPRECATED = "Deprecated"; 122 123 /** Fully-qualified {@link Deprecated Deprecated} annotation name. */ 124 private static final String FQ_DEPRECATED = "java.lang." + DEPRECATED; 125 126 /** Compiled regexp to match Javadoc tag with no argument. */ 127 private static final Pattern MATCH_DEPRECATED = 128 CommonUtils.createPattern("@(deprecated)\\s+\\S"); 129 130 /** Compiled regexp to match first part of multilineJavadoc tags. */ 131 private static final Pattern MATCH_DEPRECATED_MULTILINE_START = 132 CommonUtils.createPattern("@(deprecated)\\s*$"); 133 134 /** Compiled regexp to look for a continuation of the comment. */ 135 private static final Pattern MATCH_DEPRECATED_MULTILINE_CONT = 136 CommonUtils.createPattern("(\\*/|@|[^\\s\\*])"); 137 138 /** Multiline finished at end of comment. */ 139 private static final String END_JAVADOC = "*/"; 140 /** Multiline finished at next Javadoc. */ 141 private static final String NEXT_TAG = "@"; 142 143 /** Is deprecated element valid without javadoc. */ 144 private boolean skipNoJavadoc; 145 146 /** 147 * Set skipJavadoc value. 148 * @param skipNoJavadoc user's value of skipJavadoc 149 */ 150 public void setSkipNoJavadoc(boolean skipNoJavadoc) { 151 this.skipNoJavadoc = skipNoJavadoc; 152 } 153 154 @Override 155 public int[] getDefaultTokens() { 156 return getAcceptableTokens(); 157 } 158 159 @Override 160 public int[] getAcceptableTokens() { 161 return new int[] { 162 TokenTypes.INTERFACE_DEF, 163 TokenTypes.CLASS_DEF, 164 TokenTypes.ANNOTATION_DEF, 165 TokenTypes.ENUM_DEF, 166 TokenTypes.METHOD_DEF, 167 TokenTypes.CTOR_DEF, 168 TokenTypes.VARIABLE_DEF, 169 TokenTypes.ENUM_CONSTANT_DEF, 170 TokenTypes.ANNOTATION_FIELD_DEF, 171 }; 172 } 173 174 @Override 175 public int[] getRequiredTokens() { 176 return getAcceptableTokens(); 177 } 178 179 @Override 180 public void visitToken(final DetailAST ast) { 181 final TextBlock javadoc = 182 getFileContents().getJavadocBefore(ast.getLineNo()); 183 184 final boolean containsAnnotation = 185 AnnotationUtility.containsAnnotation(ast, DEPRECATED) 186 || AnnotationUtility.containsAnnotation(ast, FQ_DEPRECATED); 187 188 final boolean containsJavadocTag = containsJavadocTag(javadoc); 189 190 if (containsAnnotation ^ containsJavadocTag && !(skipNoJavadoc && javadoc == null)) { 191 log(ast.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED); 192 } 193 } 194 195 /** 196 * Checks to see if the text block contains a deprecated tag. 197 * 198 * @param javadoc the javadoc of the AST 199 * @return true if contains the tag 200 */ 201 private boolean containsJavadocTag(final TextBlock javadoc) { 202 boolean found = false; 203 if (javadoc != null) { 204 final String[] lines = javadoc.getText(); 205 int currentLine = javadoc.getStartLineNo() - 1; 206 207 for (int i = 0; i < lines.length; i++) { 208 currentLine++; 209 final String line = lines[i]; 210 211 final Matcher javadocNoArgMatcher = MATCH_DEPRECATED.matcher(line); 212 final Matcher noArgMultilineStart = MATCH_DEPRECATED_MULTILINE_START.matcher(line); 213 214 if (javadocNoArgMatcher.find()) { 215 if (found) { 216 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 217 JavadocTagInfo.DEPRECATED.getText()); 218 } 219 found = true; 220 } 221 else if (noArgMultilineStart.find()) { 222 found = checkTagAtTheRestOfComment(lines, found, currentLine, i); 223 } 224 } 225 } 226 return found; 227 } 228 229 /** 230 * Look for the rest of the comment if all we saw was 231 * the tag and the name. Stop when we see '*' (end of 232 * Javadoc), '{@literal @}' (start of next tag), or anything that's 233 * not whitespace or '*' characters. 234 * @param lines all lines 235 * @param foundBefore flag from parent method 236 * @param currentLine current line 237 * @param index som index 238 * @return true if Tag is found 239 */ 240 private boolean checkTagAtTheRestOfComment(String[] lines, boolean foundBefore, 241 int currentLine, int index) { 242 243 boolean found = false; 244 for (int reindex = index + 1; 245 reindex < lines.length;) { 246 final Matcher multilineCont = MATCH_DEPRECATED_MULTILINE_CONT.matcher(lines[reindex]); 247 248 if (multilineCont.find()) { 249 reindex = lines.length; 250 final String lFin = multilineCont.group(1); 251 if (lFin.equals(NEXT_TAG) || lFin.equals(END_JAVADOC)) { 252 log(currentLine, MSG_KEY_JAVADOC_MISSING); 253 if (foundBefore) { 254 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 255 JavadocTagInfo.DEPRECATED.getText()); 256 } 257 found = true; 258 } 259 else { 260 if (foundBefore) { 261 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 262 JavadocTagInfo.DEPRECATED.getText()); 263 } 264 found = true; 265 } 266 } 267 reindex++; 268 } 269 return found; 270 } 271}