001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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.AnnotationUtil; 032import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 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 */ 099@StatelessCheck 100public 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 /** Is deprecated element valid without javadoc. */ 146 private boolean skipNoJavadoc; 147 148 /** 149 * Set skipJavadoc value. 150 * @param skipNoJavadoc user's value of skipJavadoc 151 */ 152 public void setSkipNoJavadoc(boolean skipNoJavadoc) { 153 this.skipNoJavadoc = skipNoJavadoc; 154 } 155 156 @Override 157 public int[] getDefaultTokens() { 158 return getRequiredTokens(); 159 } 160 161 @Override 162 public int[] getAcceptableTokens() { 163 return getRequiredTokens(); 164 } 165 166 @Override 167 public int[] getRequiredTokens() { 168 return new int[] { 169 TokenTypes.INTERFACE_DEF, 170 TokenTypes.CLASS_DEF, 171 TokenTypes.ANNOTATION_DEF, 172 TokenTypes.ENUM_DEF, 173 TokenTypes.METHOD_DEF, 174 TokenTypes.CTOR_DEF, 175 TokenTypes.VARIABLE_DEF, 176 TokenTypes.ENUM_CONSTANT_DEF, 177 TokenTypes.ANNOTATION_FIELD_DEF, 178 }; 179 } 180 181 @Override 182 public void visitToken(final DetailAST ast) { 183 final TextBlock javadoc = 184 getFileContents().getJavadocBefore(ast.getLineNo()); 185 186 final boolean containsAnnotation = 187 AnnotationUtil.containsAnnotation(ast, DEPRECATED) 188 || AnnotationUtil.containsAnnotation(ast, FQ_DEPRECATED); 189 190 final boolean containsJavadocTag = containsJavadocTag(javadoc); 191 192 if (containsAnnotation ^ containsJavadocTag && !(skipNoJavadoc && javadoc == null)) { 193 log(ast.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED); 194 } 195 } 196 197 /** 198 * Checks to see if the text block contains a deprecated tag. 199 * 200 * @param javadoc the javadoc of the AST 201 * @return true if contains the tag 202 */ 203 private boolean containsJavadocTag(final TextBlock javadoc) { 204 boolean found = false; 205 if (javadoc != null) { 206 final String[] lines = javadoc.getText(); 207 int currentLine = javadoc.getStartLineNo() - 1; 208 209 for (int i = 0; i < lines.length; i++) { 210 currentLine++; 211 final String line = lines[i]; 212 213 final Matcher javadocNoArgMatcher = MATCH_DEPRECATED.matcher(line); 214 final Matcher noArgMultilineStart = MATCH_DEPRECATED_MULTILINE_START.matcher(line); 215 216 if (javadocNoArgMatcher.find()) { 217 if (found) { 218 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 219 JavadocTagInfo.DEPRECATED.getText()); 220 } 221 found = true; 222 } 223 else if (noArgMultilineStart.find()) { 224 checkTagAtTheRestOfComment(lines, found, currentLine, i); 225 found = true; 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 */ 242 private void checkTagAtTheRestOfComment(String[] lines, boolean foundBefore, 243 int currentLine, int index) { 244 int reindex = index + 1; 245 while (reindex <= lines.length - 1) { 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 } 254 if (foundBefore) { 255 log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, 256 JavadocTagInfo.DEPRECATED.getText()); 257 } 258 } 259 reindex++; 260 } 261 } 262 263}