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;
021
022import java.util.regex.Pattern;
023
024import com.puppycrawl.tools.checkstyle.StatelessCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
029import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
030
031/**
032 * <p>
033 * The check to ensure that lines with code do not end with comment.
034 * For the case of {@code //} comments that means that the only thing that should precede
035 * it is whitespace. It doesn't check comments if they do not end a line; for example,
036 * it accepts the following: <code>Thread.sleep( 10 /*some comment here&#42;/ );</code>
037 * Format property is intended to deal with the <code>} // while</code> example.
038 * </p>
039 * <p>
040 * Rationale: Steve McConnell in <cite>Code Complete</cite> suggests that endline
041 * comments are a bad practice. An end line comment would be one that is on
042 * the same line as actual code. For example:
043 * </p>
044 * <pre>
045 * a = b + c;      // Some insightful comment
046 * d = e / f;        // Another comment for this line
047 * </pre>
048 * <p>
049 * Quoting <cite>Code Complete</cite> for the justification:
050 * </p>
051 * <ul>
052 * <li>
053 * "The comments have to be aligned so that they do not interfere with the visual
054 * structure of the code. If you don't align them neatly, they'll make your listing
055 * look like it's been through a washing machine."
056 * </li>
057 * <li>
058 * "Endline comments tend to be hard to format...It takes time to align them.
059 * Such time is not spent learning more about the code; it's dedicated solely
060 * to the tedious task of pressing the spacebar or tab key."
061 * </li>
062 * <li>
063 * "Endline comments are also hard to maintain. If the code on any line containing
064 * an endline comment grows, it bumps the comment farther out, and all the other
065 * endline comments will have to bumped out to match. Styles that are hard to
066 * maintain aren't maintained...."
067 * </li>
068 * <li>
069 * "Endline comments also tend to be cryptic. The right side of the line doesn't
070 * offer much room and the desire to keep the comment on one line means the comment
071 * must be short. Work then goes into making the line as short as possible instead
072 * of as clear as possible. The comment usually ends up as cryptic as possible...."
073 * </li>
074 * <li>
075 * "A systemic problem with endline comments is that it's hard to write a meaningful
076 * comment for one line of code. Most endline comments just repeat the line of code,
077 * which hurts more than it helps."
078 * </li>
079 * </ul>
080 * <p>
081 * McConnell's comments on being hard to maintain when the size of the line changes
082 * are even more important in the age of automated refactorings.
083 * </p>
084 * <ul>
085 * <li>
086 * Property {@code format} - Specify pattern for strings allowed before the comment.
087 * Type is {@code java.util.regex.Pattern}.
088 * Default value is <code>"^[\s});]*$"</code>.
089 * </li>
090 * <li>
091 * Property {@code legalComment} - Define pattern for text allowed in trailing comments.
092 * (This pattern will not be applied to multiline comments and the text of
093 * the comment will be trimmed before matching.)
094 * Type is {@code java.util.regex.Pattern}.
095 * Default value is {@code null}.
096 * </li>
097 * </ul>
098 * <p>
099 * To configure the check:
100 * </p>
101 * <pre>
102 * &lt;module name=&quot;TrailingComment&quot;/&gt;
103 * </pre>
104 * <p>
105 * To configure the check so it enforces only comment on a line:
106 * </p>
107 * <pre>
108 * &lt;module name=&quot;TrailingComment&quot;&gt;
109 *   &lt;property name=&quot;format&quot; value=&quot;^\\s*$&quot;/&gt;
110 * &lt;/module&gt;
111 * </pre>
112 * <p>
113 * Example for trailing comments check to suppress specific trailing comment:
114 * </p>
115 * <pre>
116 * public class Test {
117 *   int a; // SUPPRESS CHECKSTYLE
118 *   int b; // NOPMD
119 *   int c; // NOSONAR
120 *   int d; // violation, not suppressed
121 * }
122 * </pre>
123 * <p>
124 * To configure check so that trailing comment with exact comments like "SUPPRESS CHECKSTYLE",
125 * "NOPMD", "NOSONAR" are suppressed:
126 * </p>
127 * <pre>
128 * &lt;module name="TrailingComment"/&gt;
129 * &lt;module name="SuppressionXpathSingleFilter"&gt;
130 *   &lt;property name="checks" value="TrailingCommentCheck"/&gt;
131 *   &lt;property name="query" value="//SINGLE_LINE_COMMENT
132 *       [./COMMENT_CONTENT[@text=' NOSONAR\n' or @text=' NOPMD\n'
133 *       or @text=' SUPPRESS CHECKSTYLE\n']]"/&gt;
134 * &lt;/module&gt;
135 * </pre>
136 * <p>
137 * To configure check so that trailing comment starting with "SUPPRESS CHECKSTYLE", "NOPMD",
138 * "NOSONAR" are suppressed:
139 * </p>
140 * <pre>
141 * &lt;module name="TrailingComment"/&gt; &lt;module name="SuppressionXpathSingleFilter"&gt;
142 * &lt;property name="checks" value="TrailingCommentCheck"/&gt;
143 *   &lt;property name="query" value="//SINGLE_LINE_COMMENT
144 *       [./COMMENT_CONTENT[starts-with(@text, ' NOPMD')]]"/&gt;
145 *   &lt;property name="query" value="//SINGLE_LINE_COMMENT
146 *       [./COMMENT_CONTENT[starts-with(@text, ' SUPPRESS CHECKSTYLE')]]"/&gt;
147 *   &lt;property name="query" value="//SINGLE_LINE_COMMENT
148 *       [./COMMENT_CONTENT[starts-with(@text, ' NOSONAR')]]"/&gt;
149 * &lt;/module&gt;
150 * </pre>
151 * <p>
152 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
153 * </p>
154 * <p>
155 * Violation Message Keys:
156 * </p>
157 * <ul>
158 * <li>
159 * {@code trailing.comments}
160 * </li>
161 * </ul>
162 *
163 * @noinspection HtmlTagCanBeJavadocTag
164 * @since 3.4
165 */
166@StatelessCheck
167public class TrailingCommentCheck extends AbstractCheck {
168
169    /**
170     * A key is pointing to the warning message text in "messages.properties"
171     * file.
172     */
173    public static final String MSG_KEY = "trailing.comments";
174
175    /** Specify pattern for strings to be formatted without comment specifiers. */
176    private static final Pattern FORMAT_LINE = Pattern.compile("/");
177
178    /**
179     * Define pattern for text allowed in trailing comments.
180     * (This pattern will not be applied to multiline comments and the text
181     * of the comment will be trimmed before matching.)
182     */
183    private Pattern legalComment;
184
185    /** Specify pattern for strings allowed before the comment. */
186    private Pattern format = Pattern.compile("^[\\s});]*$");
187
188    /**
189     * Setter to define pattern for text allowed in trailing comments.
190     * (This pattern will not be applied to multiline comments and the text
191     * of the comment will be trimmed before matching.)
192     *
193     * @param legalComment pattern to set.
194     */
195    public void setLegalComment(final Pattern legalComment) {
196        this.legalComment = legalComment;
197    }
198
199    /**
200     * Setter to specify pattern for strings allowed before the comment.
201     *
202     * @param pattern a pattern
203     */
204    public final void setFormat(Pattern pattern) {
205        format = pattern;
206    }
207
208    @Override
209    public boolean isCommentNodesRequired() {
210        return true;
211    }
212
213    @Override
214    public int[] getDefaultTokens() {
215        return getRequiredTokens();
216    }
217
218    @Override
219    public int[] getAcceptableTokens() {
220        return getRequiredTokens();
221    }
222
223    @Override
224    public int[] getRequiredTokens() {
225        return new int[] {
226            TokenTypes.SINGLE_LINE_COMMENT,
227            TokenTypes.BLOCK_COMMENT_BEGIN,
228        };
229    }
230
231    @Override
232    public void visitToken(DetailAST ast) {
233        if (ast.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
234            checkSingleLineComment(ast);
235        }
236        else {
237            checkBlockComment(ast);
238        }
239    }
240
241    /**
242     * Checks if single line comment is legal.
243     *
244     * @param ast Detail ast element to be checked.
245     */
246    private void checkSingleLineComment(DetailAST ast) {
247        final int lineNo = ast.getLineNo();
248        final String comment = ast.getFirstChild().getText();
249        final String line = getLines()[lineNo - 1];
250        final String lineBefore = line.substring(0, ast.getColumnNo());
251
252        if (!format.matcher(lineBefore).find()
253                && !isLegalSingleLineComment(comment)) {
254            log(ast, MSG_KEY);
255        }
256    }
257
258    /**
259     * Method to check if block comment is in correct format.
260     *
261     * @param ast Detail ast element to be checked.
262     */
263    private void checkBlockComment(DetailAST ast) {
264        final int lineNo = ast.getLineNo();
265        final String comment = ast.getFirstChild().getText();
266        String line = getLines()[lineNo - 1];
267
268        if (line.length() > ast.getLastChild().getColumnNo() + 1) {
269            line = line.substring(ast.getLastChild().getColumnNo() + 2);
270        }
271
272        line = FORMAT_LINE.matcher(line).replaceAll("");
273
274        final String lineBefore = getLines()[lineNo - 1].substring(0, ast.getColumnNo());
275
276        // do not check comment which doesn't end line
277        if ((ast.getLineNo() != ast.getLastChild().getLineNo() || CommonUtil.isBlank(line))
278                && !format.matcher(lineBefore).find()
279                && !isLegalBlockComment(ast, comment)) {
280            log(ast, MSG_KEY);
281        }
282    }
283
284    /**
285     * Checks if block comment is legal and matches to the pattern.
286     *
287     * @param ast Detail ast element to be checked.
288     * @param comment comment to check.
289     * @return true if the comment if legal.
290     */
291    private boolean isLegalBlockComment(DetailAST ast, String comment) {
292        final boolean legal;
293
294        // multi-line comment can not be legal
295        if (legalComment == null
296                || !TokenUtil.areOnSameLine(ast.getFirstChild(), ast.getLastChild())) {
297            legal = false;
298        }
299        else {
300            final String commentText = comment.trim();
301            legal = legalComment.matcher(commentText).find();
302        }
303        return legal;
304    }
305
306    /**
307     * Checks if given single line comment is legal (single-line and matches to the
308     * pattern).
309     *
310     * @param comment comment to check.
311     * @return true if the comment if legal.
312     */
313    private boolean isLegalSingleLineComment(String comment) {
314        final boolean legal;
315        if (legalComment == null) {
316            legal = false;
317        }
318        else {
319            // remove chars which start comment
320            final String commentText = comment.substring(1).trim();
321            legal = legalComment.matcher(commentText).find();
322        }
323        return legal;
324    }
325}