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 &#64;Deprecated annotation is present
37   * and the &#64;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 &#64;Deprecated annotation is used for compilers and development tools.
42   * The &#64;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 &#64;deprecated is missing from JavaDoc
63   * or &#64;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   * &lt;module name=&quot;MissingDeprecated&quot;/&gt;
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   * &#64;deprecated is missing from JavaDoc or
77   * &#64;Deprecated is missing from the element.
78   * To configure this check to allow it use:
79   * </p>
80   * <pre>
81   * &lt;module name=&quot;MissingDeprecated&quot;&gt;
82   *   &lt;property name=&quot;skipNoJavadoc&quot; value=&quot;true&quot; /&gt;
83   * &lt;/module&gt;
84   * </pre>
85   * <p>
86   * Examples of validating source code with skipNoJavadoc:
87   * </p>
88   * <pre>
89   * &#64;deprecated
90   * public static final int MY_CONST = 123456; // no violation
91   *
92   * &#47;** This javadoc is missing deprecated tag. *&#47;
93   * &#64;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      * &#64;deprecated is missing from JavaDoc or &#64;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 &#64;deprecated is missing from JavaDoc or &#64;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 }