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;
021
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.regex.Pattern;
027
028import com.puppycrawl.tools.checkstyle.StatelessCheck;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.TextBlock;
032import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
033
034/**
035 * <p>
036 * The check to ensure that requires that comments be the only thing on a line.
037 * For the case of {@code //} comments that means that the only thing that should precede
038 * it is whitespace. It doesn't check comments if they do not end a line; for example,
039 * it accepts the following: <code>Thread.sleep( 10 /*some comment here&#42;/ );</code>
040 * Format property is intended to deal with the <code>} // while</code> example.
041 * </p>
042 * <p>
043 * Rationale: Steve McConnell in <cite>Code Complete</cite> suggests that endline
044 * comments are a bad practice. An end line comment would be one that is on
045 * the same line as actual code. For example:
046 * </p>
047 * <pre>
048 * a = b + c;      // Some insightful comment
049 * d = e / f;        // Another comment for this line
050 * </pre>
051 * <p>
052 * Quoting <cite>Code Complete</cite> for the justification:
053 * </p>
054 * <ul>
055 * <li>
056 * "The comments have to be aligned so that they do not interfere with the visual
057 * structure of the code. If you don't align them neatly, they'll make your listing
058 * look like it's been through a washing machine."
059 * </li>
060 * <li>
061 * "Endline comments tend to be hard to format...It takes time to align them.
062 * Such time is not spent learning more about the code; it's dedicated solely
063 * to the tedious task of pressing the spacebar or tab key."
064 * </li>
065 * <li>
066 * "Endline comments are also hard to maintain. If the code on any line containing
067 * an endline comment grows, it bumps the comment farther out, and all the other
068 * endline comments will have to bumped out to match. Styles that are hard to
069 * maintain aren't maintained...."
070 * </li>
071 * <li>
072 * "Endline comments also tend to be cryptic. The right side of the line doesn't
073 * offer much room and the desire to keep the comment on one line means the comment
074 * must be short. Work then goes into making the line as short as possible instead
075 * of as clear as possible. The comment usually ends up as cryptic as possible...."
076 * </li>
077 * <li>
078 * "A systemic problem with endline comments is that it's hard to write a meaningful
079 * comment for one line of code. Most endline comments just repeat the line of code,
080 * which hurts more than it helps."
081 * </li>
082 * </ul>
083 * <p>
084 * McConnell's comments on being hard to maintain when the size of the line changes
085 * are even more important in the age of automated refactorings.
086 * </p>
087 * <ul>
088 * <li>
089 * Property {@code format} - Specify pattern for strings allowed before the comment.
090 * Type is {@code java.util.regex.Pattern}.
091 * Default value is <code>"^[\s});]*$"</code>.
092 * </li>
093 * <li>
094 * Property {@code legalComment} - Define pattern for text allowed in trailing comments.
095 * (This pattern will not be applied to multiline comments and the text of
096 * the comment will be trimmed before matching.)
097 * Type is {@code java.util.regex.Pattern}.
098 * Default value is {@code null}.
099 * </li>
100 * </ul>
101 * <p>
102 * To configure the check:
103 * </p>
104 * <pre>
105 * &lt;module name=&quot;TrailingComment&quot;/&gt;
106 * </pre>
107 * <p>
108 * To configure the check so it enforces only comment on a line:
109 * </p>
110 * <pre>
111 * &lt;module name=&quot;TrailingComment&quot;&gt;
112 *   &lt;property name=&quot;format&quot; value=&quot;^\\s*$&quot;/&gt;
113 * &lt;/module&gt;
114 * </pre>
115 *
116 * <p>
117 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
118 * </p>
119 * <p>
120 * Violation Message Keys:
121 * </p>
122 * <ul>
123 * <li>
124 * {@code trailing.comments}
125 * </li>
126 * </ul>
127 * @noinspection HtmlTagCanBeJavadocTag
128 * @since 3.4
129 */
130@StatelessCheck
131public class TrailingCommentCheck extends AbstractCheck {
132
133    /**
134     * A key is pointing to the warning message text in "messages.properties"
135     * file.
136     */
137    public static final String MSG_KEY = "trailing.comments";
138
139    /**
140     * Define pattern for text allowed in trailing comments.
141     * (This pattern will not be applied to multiline comments and the text
142     * of the comment will be trimmed before matching.)
143     */
144    private Pattern legalComment;
145
146    /** Specify pattern for strings allowed before the comment. */
147    private Pattern format = Pattern.compile("^[\\s});]*$");
148
149    /**
150     * Setter to define pattern for text allowed in trailing comments.
151     * (This pattern will not be applied to multiline comments and the text
152     * of the comment will be trimmed before matching.)
153     *
154     * @param legalComment pattern to set.
155     */
156    public void setLegalComment(final Pattern legalComment) {
157        this.legalComment = legalComment;
158    }
159
160    /**
161     * Setter to specify pattern for strings allowed before the comment.
162     *
163     * @param pattern a pattern
164     */
165    public final void setFormat(Pattern pattern) {
166        format = pattern;
167    }
168
169    @Override
170    public int[] getDefaultTokens() {
171        return getRequiredTokens();
172    }
173
174    @Override
175    public int[] getAcceptableTokens() {
176        return getRequiredTokens();
177    }
178
179    @Override
180    public int[] getRequiredTokens() {
181        return CommonUtil.EMPTY_INT_ARRAY;
182    }
183
184    @Override
185    public void visitToken(DetailAST ast) {
186        throw new IllegalStateException("visitToken() shouldn't be called.");
187    }
188
189    @Override
190    public void beginTree(DetailAST rootAST) {
191        final Map<Integer, TextBlock> cppComments = getFileContents()
192                .getSingleLineComments();
193        final Map<Integer, List<TextBlock>> cComments = getFileContents()
194                .getBlockComments();
195        final Set<Integer> lines = new HashSet<>();
196        lines.addAll(cppComments.keySet());
197        lines.addAll(cComments.keySet());
198
199        for (Integer lineNo : lines) {
200            final String line = getLines()[lineNo - 1];
201            final String lineBefore;
202            final TextBlock comment;
203            if (cppComments.containsKey(lineNo)) {
204                comment = cppComments.get(lineNo);
205                lineBefore = line.substring(0, comment.getStartColNo());
206            }
207            else {
208                final List<TextBlock> commentList = cComments.get(lineNo);
209                comment = commentList.get(commentList.size() - 1);
210                lineBefore = line.substring(0, comment.getStartColNo());
211
212                // do not check comment which doesn't end line
213                if (comment.getText().length == 1
214                        && !CommonUtil.isBlank(line
215                            .substring(comment.getEndColNo() + 1))) {
216                    continue;
217                }
218            }
219            if (!format.matcher(lineBefore).find()
220                && !isLegalComment(comment)) {
221                log(lineNo, MSG_KEY);
222            }
223        }
224    }
225
226    /**
227     * Checks if given comment is legal (single-line and matches to the
228     * pattern).
229     *
230     * @param comment comment to check.
231     * @return true if the comment if legal.
232     */
233    private boolean isLegalComment(final TextBlock comment) {
234        final boolean legal;
235
236        // multi-line comment can not be legal
237        if (legalComment == null || comment.getStartLineNo() != comment.getEndLineNo()) {
238            legal = false;
239        }
240        else {
241            String commentText = comment.getText()[0];
242            // remove chars which start comment
243            commentText = commentText.substring(2);
244            // if this is a C-style comment we need to remove its end
245            if (commentText.endsWith("*/")) {
246                commentText = commentText.substring(0, commentText.length() - 2);
247            }
248            commentText = commentText.trim();
249            legal = legalComment.matcher(commentText).find();
250        }
251        return legal;
252    }
253
254}