1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.annotation; 21 22 import java.util.regex.Matcher; 23 import java.util.regex.Pattern; 24 25 import com.puppycrawl.tools.checkstyle.StatelessCheck; 26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 27 import com.puppycrawl.tools.checkstyle.api.DetailAST; 28 import com.puppycrawl.tools.checkstyle.api.TextBlock; 29 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 30 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 31 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 33 34 /** 35 * <p> 36 * Verifies that both the @Deprecated annotation is present 37 * and the @deprecated javadoc tag are present when either one is present. 38 * </p> 39 * <p> 40 * Both ways of flagging deprecation serve their own purpose. 41 * The @Deprecated annotation is used for compilers and development tools. 42 * The @deprecated javadoc tag is used to document why something is deprecated 43 * and what, if any, alternatives exist. 44 * </p> 45 * <p> 46 * In order to properly mark something as deprecated both forms of 47 * deprecation should be present. 48 * </p> 49 * <p> 50 * Package deprecation is a exception to the rule of always using the 51 * javadoc tag and annotation to deprecate. Only the package-info.java 52 * file can contain a Deprecated annotation and it CANNOT contain 53 * a deprecated javadoc tag. This is the case with 54 * Sun's javadoc tool released with JDK 1.6.0_11. As a result, this check 55 * does not deal with Deprecated packages in any way. <b>No official 56 * documentation was found confirming this behavior is correct 57 * (of the javadoc tool).</b> 58 * </p> 59 * <ul> 60 * <li> 61 * Property {@code skipNoJavadoc} - Ignore cases when JavaDoc is missing, but still warns when 62 * JavaDoc is present but either @deprecated is missing from JavaDoc 63 * or @Deprecated is missing from the element. Default value is {@code false}. 64 * </li> 65 * </ul> 66 * <p> 67 * To configure the check: 68 * </p> 69 * <pre> 70 * <module name="MissingDeprecated"/> 71 * </pre> 72 * <p> 73 * In addition you can configure this check with skipNoJavadoc 74 * option to allow it to ignore cases when JavaDoc is missing, 75 * but still warns when JavaDoc is present but either 76 * @deprecated is missing from JavaDoc or 77 * @Deprecated is missing from the element. 78 * To configure this check to allow it use: 79 * </p> 80 * <pre> 81 * <module name="MissingDeprecated"> 82 * <property name="skipNoJavadoc" value="true" /> 83 * </module> 84 * </pre> 85 * <p> 86 * Examples of validating source code with skipNoJavadoc: 87 * </p> 88 * <pre> 89 * @deprecated 90 * public static final int MY_CONST = 123456; // no violation 91 * 92 * /** This javadoc is missing deprecated tag. */ 93 * @deprecated 94 * public static final int COUNTER = 10; // violation as javadoc exists 95 * </pre> 96 * 97 * @since 5.0 98 */ 99 @StatelessCheck 100 public final class MissingDeprecatedCheck extends AbstractCheck { 101 102 /** 103 * A key is pointing to the warning message text in "messages.properties" 104 * file. 105 */ 106 public static final String MSG_KEY_ANNOTATION_MISSING_DEPRECATED = 107 "annotation.missing.deprecated"; 108 109 /** 110 * A key is pointing to the warning message text in "messages.properties" 111 * file. 112 */ 113 public static final String MSG_KEY_JAVADOC_DUPLICATE_TAG = 114 "javadoc.duplicateTag"; 115 116 /** 117 * A key is pointing to the warning message text in "messages.properties" 118 * file. 119 */ 120 public static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing"; 121 122 /** {@link Deprecated Deprecated} annotation name. */ 123 private static final String DEPRECATED = "Deprecated"; 124 125 /** Fully-qualified {@link Deprecated Deprecated} annotation name. */ 126 private static final String FQ_DEPRECATED = "java.lang." + DEPRECATED; 127 128 /** Compiled regexp to match Javadoc tag with no argument. */ 129 private static final Pattern MATCH_DEPRECATED = 130 CommonUtil.createPattern("@(deprecated)\\s+\\S"); 131 132 /** Compiled regexp to match first part of multilineJavadoc tags. */ 133 private static final Pattern MATCH_DEPRECATED_MULTILINE_START = 134 CommonUtil.createPattern("@(deprecated)\\s*$"); 135 136 /** Compiled regexp to look for a continuation of the comment. */ 137 private static final Pattern MATCH_DEPRECATED_MULTILINE_CONT = 138 CommonUtil.createPattern("(\\*/|@|[^\\s\\*])"); 139 140 /** Multiline finished at end of comment. */ 141 private static final String END_JAVADOC = "*/"; 142 /** Multiline finished at next Javadoc. */ 143 private static final String NEXT_TAG = "@"; 144 145 /** 146 * Ignore cases when JavaDoc is missing, but still warns when JavaDoc is present but either 147 * @deprecated is missing from JavaDoc or @Deprecated is missing from the element. 148 */ 149 private boolean skipNoJavadoc; 150 151 /** 152 * Setter to ignore cases when JavaDoc is missing, but still warns when JavaDoc is present 153 * but either @deprecated is missing from JavaDoc or @Deprecated is missing 154 * from the element. 155 * @param skipNoJavadoc user's value of skipJavadoc 156 */ 157 public void setSkipNoJavadoc(boolean skipNoJavadoc) { 158 this.skipNoJavadoc = skipNoJavadoc; 159 } 160 161 @Override 162 public int[] getDefaultTokens() { 163 return getRequiredTokens(); 164 } 165 166 @Override 167 public int[] getAcceptableTokens() { 168 return getRequiredTokens(); 169 } 170 171 @Override 172 public int[] getRequiredTokens() { 173 return new int[] { 174 TokenTypes.INTERFACE_DEF, 175 TokenTypes.CLASS_DEF, 176 TokenTypes.ANNOTATION_DEF, 177 TokenTypes.ENUM_DEF, 178 TokenTypes.METHOD_DEF, 179 TokenTypes.CTOR_DEF, 180 TokenTypes.VARIABLE_DEF, 181 TokenTypes.ENUM_CONSTANT_DEF, 182 TokenTypes.ANNOTATION_FIELD_DEF, 183 }; 184 } 185 186 @Override 187 public void visitToken(final DetailAST ast) { 188 final TextBlock javadoc = 189 getFileContents().getJavadocBefore(ast.getLineNo()); 190 191 final boolean containsAnnotation = 192 AnnotationUtil.containsAnnotation(ast, DEPRECATED) 193 || AnnotationUtil.containsAnnotation(ast, FQ_DEPRECATED); 194 195 final boolean containsJavadocTag = containsJavadocTag(javadoc); 196 197 if (containsAnnotation ^ containsJavadocTag && !(skipNoJavadoc && javadoc == null)) { 198 log(ast.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED); 199 } 200 } 201 202 /** 203 * Checks to see if the text block contains a deprecated tag. 204 * 205 * @param javadoc the javadoc of the AST 206 * @return true if contains the tag 207 */ 208 private boolean containsJavadocTag(final TextBlock javadoc) { 209 boolean found = false; 210 if (javadoc != null) { 211 final String[] lines = javadoc.getText(); 212 int currentLine = javadoc.getStartLineNo() - 1; 213 214 for (int i = 0; i < lines.length; i++) { 215 currentLine++; 216 final String line = lines[i]; 217 218 final Matcher javadocNoArgMatcher = MATCH_DEPRECATED.matcher(line); 219 final Matcher noArgMultilineStart = MATCH_DEPRECATED_MULTILINE_START.matcher(line); 220 221 if (javadocNoArgMatcher.find()) { 222 if (found) { 223 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 224 JavadocTagInfo.DEPRECATED.getText()); 225 } 226 found = true; 227 } 228 else if (noArgMultilineStart.find()) { 229 checkTagAtTheRestOfComment(lines, found, currentLine, i); 230 found = true; 231 } 232 } 233 } 234 return found; 235 } 236 237 /** 238 * Look for the rest of the comment if all we saw was 239 * the tag and the name. Stop when we see '*' (end of 240 * Javadoc), '{@literal @}' (start of next tag), or anything that's 241 * not whitespace or '*' characters. 242 * @param lines all lines 243 * @param foundBefore flag from parent method 244 * @param currentLine current line 245 * @param index som index 246 */ 247 private void checkTagAtTheRestOfComment(String[] lines, boolean foundBefore, 248 int currentLine, int index) { 249 int reindex = index + 1; 250 while (reindex <= lines.length - 1) { 251 final Matcher multilineCont = MATCH_DEPRECATED_MULTILINE_CONT.matcher(lines[reindex]); 252 253 if (multilineCont.find()) { 254 reindex = lines.length; 255 final String lFin = multilineCont.group(1); 256 if (lFin.equals(NEXT_TAG) || lFin.equals(END_JAVADOC)) { 257 log(currentLine, MSG_KEY_JAVADOC_MISSING); 258 } 259 if (foundBefore) { 260 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 261 JavadocTagInfo.DEPRECATED.getText()); 262 } 263 } 264 reindex++; 265 } 266 } 267 268 }