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