View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.AnnotationUtility;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
33  
34  /**
35   * <p>
36   * This class is used to verify that both the
37   * {@link Deprecated Deprecated} annotation
38   * and the deprecated javadoc tag are present when
39   * either one is present.
40   * </p>
41   *
42   * <p>
43   * Both ways of flagging deprecation serve their own purpose.  The
44   * {@link Deprecated Deprecated} annotation is used for
45   * compilers and development tools.  The deprecated javadoc tag is
46   * used to document why something is deprecated and what, if any,
47   * alternatives exist.
48   * </p>
49   *
50   * <p>
51   * In order to properly mark something as deprecated both forms of
52   * deprecation should be present.
53   * </p>
54   *
55   * <p>
56   * Package deprecation is a exception to the rule of always using the
57   * javadoc tag and annotation to deprecate.  Only the package-info.java
58   * file can contain a Deprecated annotation and it CANNOT contain
59   * a deprecated javadoc tag.  This is the case with
60   * Sun's javadoc tool released with JDK 1.6.0_11.  As a result, this check
61   * does not deal with Deprecated packages in any way.  <b>No official
62   * documentation was found confirming this behavior is correct
63   * (of the javadoc tool).</b>
64   * </p>
65   *
66   * <p>
67   * To configure this check do the following:
68   * </p>
69   *
70   * <pre>
71   * &lt;module name="JavadocDeprecated"/&gt;
72   * </pre>
73   *
74   * <p>
75   * In addition you can configure this check with skipNoJavadoc
76   * option to allow it to ignore cases when JavaDoc is missing,
77   * but still warns when JavaDoc is present but either
78   * {@link Deprecated Deprecated} is missing from JavaDoc or
79   * {@link Deprecated Deprecated} is missing from the element.
80   * To configure this check to allow it use:
81   * </p>
82   *
83   * <pre>   &lt;property name="skipNoJavadoc" value="true" /&gt;</pre>
84   *
85   * <p>Examples of validating source code with skipNoJavadoc:</p>
86   *
87   * <pre>
88   * <code>
89   * {@literal @}deprecated
90   * public static final int MY_CONST = 123456; // no violation
91   *
92   * &#47;** This javadoc is missing deprecated tag. *&#47;
93   * {@literal @}deprecated
94   * public static final int COUNTER = 10; // violation as javadoc exists
95   * </code>
96   * </pre>
97   *
98   * @author Travis Schneeberger
99   */
100 @StatelessCheck
101 public final class MissingDeprecatedCheck extends AbstractCheck {
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             CommonUtils.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             CommonUtils.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             CommonUtils.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 getAcceptableTokens();
159     }
160 
161     @Override
162     public int[] getAcceptableTokens() {
163         return new int[] {
164             TokenTypes.INTERFACE_DEF,
165             TokenTypes.CLASS_DEF,
166             TokenTypes.ANNOTATION_DEF,
167             TokenTypes.ENUM_DEF,
168             TokenTypes.METHOD_DEF,
169             TokenTypes.CTOR_DEF,
170             TokenTypes.VARIABLE_DEF,
171             TokenTypes.ENUM_CONSTANT_DEF,
172             TokenTypes.ANNOTATION_FIELD_DEF,
173         };
174     }
175 
176     @Override
177     public int[] getRequiredTokens() {
178         return getAcceptableTokens();
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             AnnotationUtility.containsAnnotation(ast, DEPRECATED)
188             || AnnotationUtility.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                     found = checkTagAtTheRestOfComment(lines, found, currentLine, i);
225                 }
226             }
227         }
228         return found;
229     }
230 
231     /**
232      * Look for the rest of the comment if all we saw was
233      * the tag and the name. Stop when we see '*' (end of
234      * Javadoc), '{@literal @}' (start of next tag), or anything that's
235      *  not whitespace or '*' characters.
236      * @param lines all lines
237      * @param foundBefore flag from parent method
238      * @param currentLine current line
239      * @param index som index
240      * @return true if Tag is found
241      */
242     private boolean checkTagAtTheRestOfComment(String[] lines, boolean foundBefore,
243             int currentLine, int index) {
244 
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 }