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.javadoc;
021
022import java.util.Arrays;
023import java.util.Set;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026import java.util.stream.Collectors;
027
028import com.puppycrawl.tools.checkstyle.StatelessCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
031
032/**
033 * <p>
034 * Checks that a
035 * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#block-tags">
036 * javadoc block tag</a> appears only at the beginning of a line, ignoring
037 * leading asterisks and white space. A block tag is a token that starts with
038 * {@code @} symbol and is preceded by a whitespace. This check ignores block
039 * tags in comments and inside inline tags {&#64;code } and {&#64;literal }.
040 * </p>
041 * <p>
042 * Rationale: according to
043 * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#block-tags">
044 * the specification</a> all javadoc block tags should be placed at the beginning
045 * of a line. Tags that are not placed at the beginning are treated as plain text.
046 * To recognize intentional tag placement to text area it is better to escape the
047 * {@code @} symbol, and all non-escaped tags should be located at the beginning
048 * of the line. See NOTE section for details on how to escape.
049 * </p>
050 * <p>
051 * To place a tag explicitly as text, escape the {@code @} symbol with HTML entity
052 * &amp;#64; or place it inside {@code {@code }}, for example:
053 * </p>
054 * <pre>
055 * &#47;**
056 *  * &amp;#64;serial literal in {&#64;code &#64;serial} Javadoc tag.
057 *  *&#47;
058 * </pre>
059 * <ul>
060 * <li>
061 * Property {@code tags} - Specify the javadoc tags to process.
062 * Default value is {@code author, deprecated, exception, hidden, param, provides,
063 * return, see, serial, serialData, serialField, since, throws, uses, version}.
064 * </li>
065 * <li>
066 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
067 * if the Javadoc being examined by this check violates the tight html rules defined at
068 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">Tight-HTML Rules</a>.
069 * Default value is {@code false}.
070 * </li>
071 * </ul>
072 * <p>
073 * To configure the default check:
074 * </p>
075 * <pre>
076 * &lt;module name="JavadocBlockTagLocation"/&gt;
077 * </pre>
078 * <p>
079 * Example:
080 * </p>
081 * <pre>
082 * &#47;**
083 *  * Escaped tag &amp;#64;version (OK)
084 *  * Plain text with {&#64;code &#64;see} (OK)
085 *  * A @custom tag (OK)
086 *  * <!-- @see commented out (OK) -->
087 *  * email@author (OK)
088 *  * (@param in parentheses) (OK)
089 *  * '@param in single quotes' (OK)
090 *  * &#64;since 1.0 (OK)
091 *  * text &#64;return (violation)
092 *  * * &#64;param (violation)
093 * +* &#64;serial (violation)
094 *  * &#64;see first (OK) &#64;see second (violation)
095 *  *&#47;
096 * public int field;
097 * </pre>
098 * <p>
099 * To configure the check to verify tags from
100 * <a href="https://openjdk.java.net/jeps/8068562">JEP 8068562</a> only:
101 * </p>
102 * <pre>
103 * &lt;module name="JavadocBlockTagLocation"&gt;
104 *   &lt;property name="tags" value="apiNote, implSpec, implNote"/&gt;
105 * &lt;/module&gt;
106 * </pre>
107 * <p>
108 * To configure the check to verify all default tags and some custom tags in addition:
109 * </p>
110 * <pre>
111 * &lt;module name="JavadocBlockTagLocation"&gt;
112 *   &lt;!-- default tags --&gt;
113 *   &lt;property name="tags" value="author, deprecated, exception, hidden"/&gt;
114 *   &lt;property name="tags" value="param, provides, return, see, serial"/&gt;
115 *   &lt;property name="tags" value="serialData, serialField, since, throws"/&gt;
116 *   &lt;property name="tags" value="uses, version"/&gt;
117 *   &lt;!-- additional tags used in the project --&gt;
118 *   &lt;property name="tags" value="noinspection"/&gt;
119 * &lt;/module&gt;
120 * </pre>
121 *
122 * @since 8.24
123 */
124@StatelessCheck
125public class JavadocBlockTagLocationCheck extends AbstractJavadocCheck {
126
127    /**
128     * A key is pointing to the warning message text in "messages.properties" file.
129     */
130    public static final String MSG_BLOCK_TAG_LOCATION = "javadoc.blockTagLocation";
131
132    /**
133     * This regexp is used to extract the javadoc tags.
134     */
135    private static final Pattern JAVADOC_BLOCK_TAG_PATTERN = Pattern.compile("\\s@(\\w+)");
136
137    /**
138     * Block tags from Java 11
139     * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html">
140     * Documentation Comment Specification</a>.
141     */
142    private static final String[] DEFAULT_TAGS = {
143        "author",
144        "deprecated",
145        "exception",
146        "hidden",
147        "param",
148        "provides",
149        "return",
150        "see",
151        "serial",
152        "serialData",
153        "serialField",
154        "since",
155        "throws",
156        "uses",
157        "version",
158    };
159
160    /**
161     * Specify the javadoc tags to process.
162     */
163    private Set<String> tags;
164
165    /**
166     * Creates a new {@code JavadocBlockTagLocationCheck} instance with default settings.
167     */
168    public JavadocBlockTagLocationCheck() {
169        setTags(DEFAULT_TAGS);
170    }
171
172    /**
173     * Setter to specify the javadoc tags to process.
174     *
175     * @param values user's values.
176     */
177    public final void setTags(String... values) {
178        tags = Arrays.stream(values).collect(Collectors.toSet());
179    }
180
181    /**
182     * The javadoc tokens that this check must be registered for. According to
183     * <a href="https://docs.oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#block-tags">
184     * the specs</a> each block tag must appear at the beginning of a line, otherwise
185     * it will be interpreted as a plain text. This check looks for a block tag
186     * in the javadoc text, thus it needs the {@code TEXT} tokens.
187     *
188     * @return the javadoc token set this must be registered for.
189     * @see JavadocTokenTypes
190     */
191    @Override
192    public int[] getRequiredJavadocTokens() {
193        return new int[] {
194            JavadocTokenTypes.TEXT,
195        };
196    }
197
198    @Override
199    public int[] getAcceptableJavadocTokens() {
200        return getRequiredJavadocTokens();
201    }
202
203    @Override
204    public int[] getDefaultJavadocTokens() {
205        return getRequiredJavadocTokens();
206    }
207
208    @Override
209    public void visitJavadocToken(DetailNode ast) {
210        if (!isCommentOrInlineTag(ast.getParent())) {
211            final Matcher tagMatcher = JAVADOC_BLOCK_TAG_PATTERN.matcher(ast.getText());
212            while (tagMatcher.find()) {
213                final String tagName = tagMatcher.group(1);
214                if (tags.contains(tagName)) {
215                    log(ast.getLineNumber(), MSG_BLOCK_TAG_LOCATION, tagName);
216                }
217            }
218        }
219    }
220
221    /**
222     * Checks if the node can contain an unescaped block tag without violation.
223     *
224     * @param node to check
225     * @return {@code true} if node is {@code @code}, {@code @literal} or HTML comment.
226     */
227    private static boolean isCommentOrInlineTag(DetailNode node) {
228        return node.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG
229                || node.getType() == JavadocTokenTypes.HTML_COMMENT;
230    }
231
232}