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;
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 * Default value is <code>"^[\s});]*$"</code>.
091 * </li>
092 * <li>
093 * Property {@code legalComment} - Define pattern for text allowed in trailing comments.
094 * (This pattern will not be applied to multiline comments and the text of
095 * the comment will be trimmed before matching.)
096 * Default value is {@code null}.
097 * </li>
098 * </ul>
099 * <p>
100 * To configure the check:
101 * </p>
102 * <pre>
103 * &lt;module name=&quot;TrailingComment&quot;/&gt;
104 * </pre>
105 * <p>
106 * To configure the check so it enforces only comment on a line:
107 * </p>
108 * <pre>
109 * &lt;module name=&quot;TrailingComment&quot;&gt;
110 *   &lt;property name=&quot;format&quot; value=&quot;^\\s*$&quot;/&gt;
111 * &lt;/module&gt;
112 * </pre>
113 *
114 * @noinspection HtmlTagCanBeJavadocTag
115 * @since 3.4
116 */
117@StatelessCheck
118public class TrailingCommentCheck extends AbstractCheck {
119
120    /**
121     * A key is pointing to the warning message text in "messages.properties"
122     * file.
123     */
124    public static final String MSG_KEY = "trailing.comments";
125
126    /**
127     * Define pattern for text allowed in trailing comments.
128     * (This pattern will not be applied to multiline comments and the text
129     * of the comment will be trimmed before matching.)
130     */
131    private Pattern legalComment;
132
133    /** Specify pattern for strings allowed before the comment. */
134    private Pattern format = Pattern.compile("^[\\s});]*$");
135
136    /**
137     * Setter to define pattern for text allowed in trailing comments.
138     * (This pattern will not be applied to multiline comments and the text
139     * of the comment will be trimmed before matching.)
140     *
141     * @param legalComment pattern to set.
142     */
143    public void setLegalComment(final Pattern legalComment) {
144        this.legalComment = legalComment;
145    }
146
147    /**
148     * Setter to specify pattern for strings allowed before the comment.
149     * @param pattern a pattern
150     */
151    public final void setFormat(Pattern pattern) {
152        format = pattern;
153    }
154
155    @Override
156    public int[] getDefaultTokens() {
157        return getRequiredTokens();
158    }
159
160    @Override
161    public int[] getAcceptableTokens() {
162        return getRequiredTokens();
163    }
164
165    @Override
166    public int[] getRequiredTokens() {
167        return CommonUtil.EMPTY_INT_ARRAY;
168    }
169
170    @Override
171    public void visitToken(DetailAST ast) {
172        throw new IllegalStateException("visitToken() shouldn't be called.");
173    }
174
175    @Override
176    public void beginTree(DetailAST rootAST) {
177        final Map<Integer, TextBlock> cppComments = getFileContents()
178                .getSingleLineComments();
179        final Map<Integer, List<TextBlock>> cComments = getFileContents()
180                .getBlockComments();
181        final Set<Integer> lines = new HashSet<>();
182        lines.addAll(cppComments.keySet());
183        lines.addAll(cComments.keySet());
184
185        for (Integer lineNo : lines) {
186            final String line = getLines()[lineNo - 1];
187            final String lineBefore;
188            final TextBlock comment;
189            if (cppComments.containsKey(lineNo)) {
190                comment = cppComments.get(lineNo);
191                lineBefore = line.substring(0, comment.getStartColNo());
192            }
193            else {
194                final List<TextBlock> commentList = cComments.get(lineNo);
195                comment = commentList.get(commentList.size() - 1);
196                lineBefore = line.substring(0, comment.getStartColNo());
197
198                // do not check comment which doesn't end line
199                if (comment.getText().length == 1
200                        && !CommonUtil.isBlank(line
201                            .substring(comment.getEndColNo() + 1))) {
202                    continue;
203                }
204            }
205            if (!format.matcher(lineBefore).find()
206                && !isLegalComment(comment)) {
207                log(lineNo, MSG_KEY);
208            }
209        }
210    }
211
212    /**
213     * Checks if given comment is legal (single-line and matches to the
214     * pattern).
215     * @param comment comment to check.
216     * @return true if the comment if legal.
217     */
218    private boolean isLegalComment(final TextBlock comment) {
219        final boolean legal;
220
221        // multi-line comment can not be legal
222        if (legalComment == null || comment.getStartLineNo() != comment.getEndLineNo()) {
223            legal = false;
224        }
225        else {
226            String commentText = comment.getText()[0];
227            // remove chars which start comment
228            commentText = commentText.substring(2);
229            // if this is a C-style comment we need to remove its end
230            if (commentText.endsWith("*/")) {
231                commentText = commentText.substring(0, commentText.length() - 2);
232            }
233            commentText = commentText.trim();
234            legal = legalComment.matcher(commentText).find();
235        }
236        return legal;
237    }
238
239}