001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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.Arrays;
023import java.util.HashSet;
024import java.util.Set;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.DetailNode;
029import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
032import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
033import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
034import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
035
036/**
037 * <p>
038 * Verifies that the annotation {@code @Deprecated} and the Javadoc tag
039 * {@code @deprecated} are both present when either of them is present.
040 * </p>
041 * <p>
042 * Both ways of flagging deprecation serve their own purpose.
043 * The &#64;Deprecated annotation is used for compilers and development tools.
044 * The &#64;deprecated javadoc tag is used to document why something is deprecated
045 * and what, if any, alternatives exist.
046 * </p>
047 * <p>
048 * In order to properly mark something as deprecated both forms of
049 * deprecation should be present.
050 * </p>
051 * <p>
052 * Package deprecation is a exception to the rule of always using the
053 * javadoc tag and annotation to deprecate.  It is not clear if the javadoc
054 * tool will support it or not as newer versions keep flip flopping on if
055 * it is supported or will cause an error. See
056 * <a href="https://bugs.openjdk.java.net/browse/JDK-8160601">JDK-8160601</a>.
057 * The deprecated javadoc tag is currently the only way to say why the package
058 * is deprecated and what to use instead.  Until this is resolved, if you don't
059 * want to print violations on package-info, you can use a
060 * <a href="https://checkstyle.org/config_filters.html">filter</a> to ignore
061 * these files until the javadoc tool faithfully supports it. An example config
062 * using SuppressionSingleFilter is:
063 * </p>
064 * <pre>
065 * &lt;!-- required till https://bugs.openjdk.java.net/browse/JDK-8160601 --&gt;
066 * &lt;module name="SuppressionSingleFilter"&gt;
067 *     &lt;property name="checks" value="MissingDeprecatedCheck"/&gt;
068 *     &lt;property name="files" value="package-info\.java"/&gt;
069 * &lt;/module&gt;
070 * </pre>
071 * <ul>
072 * <li>
073 * Property {@code violateExecutionOnNonTightHtml} - Control when to
074 * print violations if the Javadoc being examined by this check violates the
075 * tight html rules defined at
076 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
077 * Tight-HTML Rules</a>.
078 * Type is {@code boolean}.
079 * Default value is {@code false}.
080 * </li>
081 * </ul>
082 * <p>
083 * To configure the check:
084 * </p>
085 * <pre>
086 * &lt;module name=&quot;MissingDeprecated&quot;/&gt;
087 * </pre>
088 * <p>
089 * Example:
090 * </p>
091 * <pre>
092 * &#64;Deprecated
093 * public static final int MY_CONST = 13; // ok
094 *
095 * &#47;** This javadoc is missing deprecated tag. *&#47;
096 * &#64;Deprecated
097 * public static final int COUNTER = 10; // violation
098 *
099 * &#47;**
100 *  * &#64;deprecated
101 *  * &lt;p&gt;&lt;/p&gt;
102 *  *&#47;
103 * &#64;Deprecated
104 * public static final int NUM = 123456; // ok
105 *
106 * &#47;**
107 *  * &#64;deprecated
108 *  * &lt;p&gt;
109 *  *&#47;
110 * &#64;Deprecated
111 * public static final int CONST = 12; // ok
112 * </pre>
113 * <p>
114 * To configure the check such that it prints violation
115 * messages if tight HTML rules are not obeyed
116 * </p>
117 * <pre>
118 * &lt;module name="MissingDeprecated"&gt;
119 *   &lt;property name="violateExecutionOnNonTightHtml" value="true"/&gt;
120 * &lt;/module&gt;
121 * </pre>
122 * <p>
123 * Example:
124 * </p>
125 * <pre>
126 * &#64;Deprecated
127 * public static final int MY_CONST = 13; // ok
128 *
129 * &#47;** This javadoc is missing deprecated tag. *&#47;
130 * &#64;Deprecated
131 * public static final int COUNTER = 10; // violation
132 *
133 * &#47;**
134 *  * &#64;deprecated
135 *  * &lt;p&gt;&lt;/p&gt;
136 *  *&#47;
137 * &#64;Deprecated
138 * public static final int NUM = 123456; // ok
139 *
140 * &#47;**
141 *  * &#64;deprecated
142 *  * &lt;p&gt;
143 *  *&#47;
144 * &#64;Deprecated
145 * public static final int CONST = 12; // violation, tight HTML rules not obeyed
146 * </pre>
147 * <p>
148 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
149 * </p>
150 * <p>
151 * Violation Message Keys:
152 * </p>
153 * <ul>
154 * <li>
155 * {@code annotation.missing.deprecated}
156 * </li>
157 * <li>
158 * {@code javadoc.duplicateTag}
159 * </li>
160 * <li>
161 * {@code javadoc.missed.html.close}
162 * </li>
163 * <li>
164 * {@code javadoc.parse.rule.error}
165 * </li>
166 * <li>
167 * {@code javadoc.wrong.singleton.html.tag}
168 * </li>
169 * </ul>
170 *
171 * @since 5.0
172 */
173@StatelessCheck
174public final class MissingDeprecatedCheck extends AbstractJavadocCheck {
175
176    /**
177     * A key is pointing to the warning message text in "messages.properties"
178     * file.
179     */
180    public static final String MSG_KEY_ANNOTATION_MISSING_DEPRECATED =
181            "annotation.missing.deprecated";
182
183    /**
184     * A key is pointing to the warning message text in "messages.properties"
185     * file.
186     */
187    public static final String MSG_KEY_JAVADOC_DUPLICATE_TAG =
188            "javadoc.duplicateTag";
189
190    /** {@link Deprecated Deprecated} annotation name. */
191    private static final String DEPRECATED = "Deprecated";
192
193    /** Fully-qualified {@link Deprecated Deprecated} annotation name. */
194    private static final String FQ_DEPRECATED = "java.lang." + DEPRECATED;
195
196    /** List of token types to find parent of. */
197    private static final Set<Integer> TYPES_HASH_SET = new HashSet<>(Arrays.asList(
198            TokenTypes.TYPE, TokenTypes.MODIFIERS, TokenTypes.ANNOTATION,
199            TokenTypes.ANNOTATIONS, TokenTypes.ARRAY_DECLARATOR,
200            TokenTypes.TYPE_PARAMETERS, TokenTypes.DOT));
201
202    @Override
203    public int[] getDefaultJavadocTokens() {
204        return getRequiredJavadocTokens();
205    }
206
207    @Override
208    public int[] getRequiredJavadocTokens() {
209        return new int[] {
210            JavadocTokenTypes.JAVADOC,
211        };
212    }
213
214    @Override
215    public void visitJavadocToken(DetailNode ast) {
216        final DetailAST parentAst = getParent(getBlockCommentAst());
217
218        final boolean containsAnnotation =
219            AnnotationUtil.containsAnnotation(parentAst, DEPRECATED)
220            || AnnotationUtil.containsAnnotation(parentAst, FQ_DEPRECATED);
221
222        final boolean containsJavadocTag = containsDeprecatedTag(ast);
223
224        if (containsAnnotation ^ containsJavadocTag) {
225            log(parentAst.getLineNo(), MSG_KEY_ANNOTATION_MISSING_DEPRECATED);
226        }
227    }
228
229    /**
230     * Checks to see if the javadoc contains a deprecated tag.
231     *
232     * @param javadoc the javadoc of the AST
233     * @return true if contains the tag
234     */
235    private boolean containsDeprecatedTag(DetailNode javadoc) {
236        boolean found = false;
237        for (DetailNode child : javadoc.getChildren()) {
238            if (child.getType() == JavadocTokenTypes.JAVADOC_TAG
239                    && child.getChildren()[0].getType() == JavadocTokenTypes.DEPRECATED_LITERAL) {
240                if (found) {
241                    log(child.getLineNumber(), MSG_KEY_JAVADOC_DUPLICATE_TAG,
242                            JavadocTagInfo.DEPRECATED.getText());
243                }
244                found = true;
245            }
246        }
247        return found;
248    }
249
250    /**
251     * Returns the parent node of the comment.
252     *
253     * @param commentBlock child node.
254     * @return parent node.
255     */
256    private static DetailAST getParent(DetailAST commentBlock) {
257        DetailAST result = commentBlock.getParent();
258
259        if (TokenUtil.isRootNode(result)) {
260            result = commentBlock.getNextSibling();
261        }
262
263        while (true) {
264            final int type = result.getType();
265            if (TYPES_HASH_SET.contains(type)) {
266                result = result.getParent();
267            }
268            else if (type == TokenTypes.SINGLE_LINE_COMMENT) {
269                result = result.getNextSibling();
270            }
271            else {
272                break;
273            }
274        }
275
276        return result;
277    }
278
279}