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