001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.DetailNode;
024import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
025import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
026import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
027
028/**
029 * <p>
030 * Checks the Javadoc paragraph.
031 * </p>
032 * <p>
033 * Checks that:
034 * </p>
035 * <ul>
036 * <li>There is one blank line between each of two paragraphs
037 * and one blank line before the at-clauses block if it is present.</li>
038 * <li>Each paragraph but the first has &lt;p&gt; immediately
039 * before the first word, with no space after.</li>
040 * </ul>
041 * <ul>
042 * <li>
043 * Property {@code violateExecutionOnNonTightHtml} - Control when to print violations
044 * if the Javadoc being examined by this check violates the tight html rules defined at
045 * <a href="https://checkstyle.org/writingjavadocchecks.html#Tight-HTML_rules">
046 * Tight-HTML Rules</a>.
047 * Default value is {@code false}.
048 * </li>
049 * <li>
050 * Property {@code allowNewlineParagraph} - Control whether the &lt;p&gt; tag
051 * should be placed immediately before the first word.
052 * Default value is {@code true}.
053 * </li>
054 * </ul>
055 * <p>
056 * To configure the default check:
057 * </p>
058 * <pre>
059 * &lt;module name=&quot;JavadocParagraph&quot;/&gt;
060 * </pre>
061 * <p>
062 * By default, the check will report a violation if there is a new line
063 * or whitespace after the &lt;p&gt; tag:
064 * </p>
065 * <pre>
066 * &#47;**
067 *  * No tag (ok).
068 *  *
069 *  * &lt;p&gt;Tag immediately before the text (ok).
070 *  * &lt;p&gt;No blank line before the tag (violation).
071 *  *
072 *  * &lt;p&gt;
073 *  * New line after tag (violation).
074 *  *
075 *  * &lt;p&gt; Whitespace after tag (violation).
076 *  *
077 *  *&#47;
078 * public class TestClass {
079 * }
080 * </pre>
081 * <p>
082 * To allow newlines and spaces immediately after the &lt;p&gt; tag:
083 * </p>
084 * <pre>
085 * &lt;module name=&quot;JavadocParagraph&quot;&gt;
086 *   &lt;property name=&quot;allowNewlineParagraph&quot; value=&quot;false&quot;/&gt;
087 * &lt;/module&gt;
088 * </pre>
089 * <p>
090 * In case of {@code allowNewlineParagraph} set to {@code false}
091 * the following example will not have any violations:
092 * </p>
093 * <pre>
094 * &#47;**
095 *  * No tag (ok).
096 *  *
097 *  * &lt;p&gt;Tag immediately before the text (ok).
098 *  * &lt;p&gt;No blank line before the tag (violation).
099 *  *
100 *  * &lt;p&gt;
101 *  * New line after tag (ok).
102 *  *
103 *  * &lt;p&gt; Whitespace after tag (ok).
104 *  *
105 *  *&#47;
106 * public class TestClass {
107 * }
108 * </pre>
109 *
110 * @since 6.0
111 */
112@StatelessCheck
113public class JavadocParagraphCheck extends AbstractJavadocCheck {
114
115    /**
116     * A key is pointing to the warning message text in "messages.properties"
117     * file.
118     */
119    public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after";
120
121    /**
122     * A key is pointing to the warning message text in "messages.properties"
123     * file.
124     */
125    public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before";
126
127    /**
128     * A key is pointing to the warning message text in "messages.properties"
129     * file.
130     */
131    public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph";
132
133    /**
134     * A key is pointing to the warning message text in "messages.properties"
135     * file.
136     */
137    public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag";
138
139    /**
140     * Control whether the &lt;p&gt; tag should be placed immediately before the first word.
141     */
142    private boolean allowNewlineParagraph = true;
143
144    /**
145     * Setter to control whether the &lt;p&gt; tag should be placed
146     * immediately before the first word.
147     *
148     * @param value value to set.
149     */
150    public void setAllowNewlineParagraph(boolean value) {
151        allowNewlineParagraph = value;
152    }
153
154    @Override
155    public int[] getDefaultJavadocTokens() {
156        return new int[] {
157            JavadocTokenTypes.NEWLINE,
158            JavadocTokenTypes.HTML_ELEMENT,
159        };
160    }
161
162    @Override
163    public int[] getRequiredJavadocTokens() {
164        return getAcceptableJavadocTokens();
165    }
166
167    @Override
168    public void visitJavadocToken(DetailNode ast) {
169        if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
170            checkEmptyLine(ast);
171        }
172        else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
173                && JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) {
174            checkParagraphTag(ast);
175        }
176    }
177
178    /**
179     * Determines whether or not the next line after empty line has paragraph tag in the beginning.
180     * @param newline NEWLINE node.
181     */
182    private void checkEmptyLine(DetailNode newline) {
183        final DetailNode nearestToken = getNearestNode(newline);
184        if (nearestToken.getType() == JavadocTokenTypes.TEXT
185                && !CommonUtil.isBlank(nearestToken.getText())) {
186            log(newline.getLineNumber(), MSG_TAG_AFTER);
187        }
188    }
189
190    /**
191     * Determines whether or not the line with paragraph tag has previous empty line.
192     * @param tag html tag.
193     */
194    private void checkParagraphTag(DetailNode tag) {
195        final DetailNode newLine = getNearestEmptyLine(tag);
196        if (isFirstParagraph(tag)) {
197            log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH);
198        }
199        else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
200            log(tag.getLineNumber(), MSG_LINE_BEFORE);
201        }
202        if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) {
203            log(tag.getLineNumber(), MSG_MISPLACED_TAG);
204        }
205    }
206
207    /**
208     * Returns nearest node.
209     * @param node DetailNode node.
210     * @return nearest node.
211     */
212    private static DetailNode getNearestNode(DetailNode node) {
213        DetailNode tag = JavadocUtil.getNextSibling(node);
214        while (tag.getType() == JavadocTokenTypes.LEADING_ASTERISK
215                || tag.getType() == JavadocTokenTypes.NEWLINE) {
216            tag = JavadocUtil.getNextSibling(tag);
217        }
218        return tag;
219    }
220
221    /**
222     * Determines whether or not the line is empty line.
223     * @param newLine NEWLINE node.
224     * @return true, if line is empty line.
225     */
226    private static boolean isEmptyLine(DetailNode newLine) {
227        boolean result = false;
228        DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
229        if (previousSibling != null
230                && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
231            if (previousSibling.getType() == JavadocTokenTypes.TEXT
232                    && CommonUtil.isBlank(previousSibling.getText())) {
233                previousSibling = JavadocUtil.getPreviousSibling(previousSibling);
234            }
235            result = previousSibling != null
236                    && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
237        }
238        return result;
239    }
240
241    /**
242     * Determines whether or not the line with paragraph tag is first line in javadoc.
243     * @param paragraphTag paragraph tag.
244     * @return true, if line with paragraph tag is first line in javadoc.
245     */
246    private static boolean isFirstParagraph(DetailNode paragraphTag) {
247        boolean result = true;
248        DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag);
249        while (previousNode != null) {
250            if (previousNode.getType() == JavadocTokenTypes.TEXT
251                    && !CommonUtil.isBlank(previousNode.getText())
252                || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
253                    && previousNode.getType() != JavadocTokenTypes.NEWLINE
254                    && previousNode.getType() != JavadocTokenTypes.TEXT) {
255                result = false;
256                break;
257            }
258            previousNode = JavadocUtil.getPreviousSibling(previousNode);
259        }
260        return result;
261    }
262
263    /**
264     * Finds and returns nearest empty line in javadoc.
265     * @param node DetailNode node.
266     * @return Some nearest empty line in javadoc.
267     */
268    private static DetailNode getNearestEmptyLine(DetailNode node) {
269        DetailNode newLine = JavadocUtil.getPreviousSibling(node);
270        while (newLine != null) {
271            final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
272            if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
273                break;
274            }
275            newLine = previousSibling;
276        }
277        return newLine;
278    }
279
280    /**
281     * Tests whether the paragraph tag is immediately followed by the text.
282     * @param tag html tag.
283     * @return true, if the paragraph tag is immediately followed by the text.
284     */
285    private static boolean isImmediatelyFollowedByText(DetailNode tag) {
286        final DetailNode nextSibling = JavadocUtil.getNextSibling(tag);
287        return nextSibling.getType() == JavadocTokenTypes.NEWLINE
288                || nextSibling.getType() == JavadocTokenTypes.EOF
289                || CommonUtil.startsWithChar(nextSibling.getText(), ' ');
290    }
291
292}