001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.Arrays;
023import java.util.regex.Pattern;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
031
032/**
033 * <p>
034 * The check to ensure that lines with code do not end with comment.
035 * For the case of {@code //} comments that means that the only thing that should precede
036 * it is whitespace. It doesn't check comments if they do not end a line; for example,
037 * it accepts the following: <code>Thread.sleep( 10 /*some comment here&#42;/ );</code>
038 * Format property is intended to deal with the <code>} // while</code> example.
039 * </p>
040 * <p>
041 * Rationale: Steve McConnell in <cite>Code Complete</cite> suggests that endline
042 * comments are a bad practice. An end line comment would be one that is on
043 * the same line as actual code. For example:
044 * </p>
045 * <pre>
046 * a = b + c;      // Some insightful comment
047 * d = e / f;        // Another comment for this line
048 * </pre>
049 * <p>
050 * Quoting <cite>Code Complete</cite> for the justification:
051 * </p>
052 * <ul>
053 * <li>
054 * "The comments have to be aligned so that they do not interfere with the visual
055 * structure of the code. If you don't align them neatly, they'll make your listing
056 * look like it's been through a washing machine."
057 * </li>
058 * <li>
059 * "Endline comments tend to be hard to format...It takes time to align them.
060 * Such time is not spent learning more about the code; it's dedicated solely
061 * to the tedious task of pressing the spacebar or tab key."
062 * </li>
063 * <li>
064 * "Endline comments are also hard to maintain. If the code on any line containing
065 * an endline comment grows, it bumps the comment farther out, and all the other
066 * endline comments will have to bumped out to match. Styles that are hard to
067 * maintain aren't maintained...."
068 * </li>
069 * <li>
070 * "Endline comments also tend to be cryptic. The right side of the line doesn't
071 * offer much room and the desire to keep the comment on one line means the comment
072 * must be short. Work then goes into making the line as short as possible instead
073 * of as clear as possible. The comment usually ends up as cryptic as possible...."
074 * </li>
075 * <li>
076 * "A systemic problem with endline comments is that it's hard to write a meaningful
077 * comment for one line of code. Most endline comments just repeat the line of code,
078 * which hurts more than it helps."
079 * </li>
080 * </ul>
081 * <p>
082 * McConnell's comments on being hard to maintain when the size of the line changes
083 * are even more important in the age of automated refactorings.
084 * </p>
085 * <ul>
086 * <li>
087 * Property {@code format} - Specify pattern for strings allowed before the comment.
088 * Type is {@code java.util.regex.Pattern}.
089 * Default value is <code>"^[\s});]*$"</code>.
090 * </li>
091 * <li>
092 * Property {@code legalComment} - Define pattern for text allowed in trailing comments.
093 * This pattern will not be applied to multiline comments.
094 * Type is {@code java.util.regex.Pattern}.
095 * Default value is {@code null}.
096 * </li>
097 * </ul>
098 * <p>
099 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
100 * </p>
101 * <p>
102 * Violation Message Keys:
103 * </p>
104 * <ul>
105 * <li>
106 * {@code trailing.comments}
107 * </li>
108 * </ul>
109 *
110 * @noinspection HtmlTagCanBeJavadocTag
111 * @noinspectionreason HtmlTagCanBeJavadocTag - encoded symbols were not decoded
112 *      when replaced with Javadoc tag
113 * @since 3.4
114 */
115@StatelessCheck
116public class TrailingCommentCheck extends AbstractCheck {
117
118    /**
119     * A key is pointing to the warning message text in "messages.properties"
120     * file.
121     */
122    public static final String MSG_KEY = "trailing.comments";
123
124    /** Specify pattern for strings to be formatted without comment specifiers. */
125    private static final Pattern FORMAT_LINE = Pattern.compile("/");
126
127    /**
128     * Define pattern for text allowed in trailing comments.
129     * This pattern will not be applied to multiline comments.
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.
139     *
140     * @param legalComment pattern to set.
141     * @since 4.2
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     *
150     * @param pattern a pattern
151     * @since 3.4
152     */
153    public final void setFormat(Pattern pattern) {
154        format = pattern;
155    }
156
157    @Override
158    public boolean isCommentNodesRequired() {
159        return true;
160    }
161
162    @Override
163    public int[] getDefaultTokens() {
164        return getRequiredTokens();
165    }
166
167    @Override
168    public int[] getAcceptableTokens() {
169        return getRequiredTokens();
170    }
171
172    @Override
173    public int[] getRequiredTokens() {
174        return new int[] {
175            TokenTypes.SINGLE_LINE_COMMENT,
176            TokenTypes.BLOCK_COMMENT_BEGIN,
177        };
178    }
179
180    @Override
181    public void visitToken(DetailAST ast) {
182        if (ast.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
183            checkSingleLineComment(ast);
184        }
185        else {
186            checkBlockComment(ast);
187        }
188    }
189
190    /**
191     * Checks if single-line comment is legal.
192     *
193     * @param ast Detail ast element to be checked.
194     */
195    private void checkSingleLineComment(DetailAST ast) {
196        final int lineNo = ast.getLineNo();
197        final String comment = ast.getFirstChild().getText();
198        final int[] lineBeforeCodePoints =
199                Arrays.copyOfRange(getLineCodePoints(lineNo - 1), 0, ast.getColumnNo());
200        final String lineBefore = new String(lineBeforeCodePoints, 0, lineBeforeCodePoints.length);
201
202        if (!format.matcher(lineBefore).find() && !isLegalCommentContent(comment)) {
203            log(ast, MSG_KEY);
204        }
205    }
206
207    /**
208     * Method to check if block comment is in correct format.
209     *
210     * @param ast Detail ast element to be checked.
211     */
212    private void checkBlockComment(DetailAST ast) {
213        final int lineNo = ast.getLineNo();
214        final DetailAST firstChild = ast.getFirstChild();
215        final DetailAST lastChild = ast.getLastChild();
216        final String comment = firstChild.getText();
217        int[] lineCodePoints = getLineCodePoints(lineNo - 1);
218
219        if (lineCodePoints.length > lastChild.getColumnNo() + 1) {
220            lineCodePoints = Arrays.copyOfRange(lineCodePoints,
221                    lastChild.getColumnNo() + 2, lineCodePoints.length);
222        }
223
224        String line = new String(lineCodePoints, 0, lineCodePoints.length);
225        line = FORMAT_LINE.matcher(line).replaceAll("");
226
227        final int[] lineBeforeCodePoints =
228                Arrays.copyOfRange(getLineCodePoints(lineNo - 1), 0, ast.getColumnNo());
229        final String lineBefore = new String(lineBeforeCodePoints, 0, lineBeforeCodePoints.length);
230        final boolean isCommentAtEndOfLine = ast.getLineNo() != lastChild.getLineNo()
231                || CommonUtil.isBlank(line);
232        final boolean isLegalBlockComment = isLegalCommentContent(comment)
233                && TokenUtil.areOnSameLine(firstChild, lastChild)
234                || format.matcher(lineBefore).find();
235
236        if (isCommentAtEndOfLine && !isLegalBlockComment) {
237            log(ast, MSG_KEY);
238        }
239    }
240
241    /**
242     * Checks if given comment content is legal.
243     *
244     * @param commentContent comment content to check.
245     * @return true if the content is legal.
246     */
247    private boolean isLegalCommentContent(String commentContent) {
248        return legalComment != null && legalComment.matcher(commentContent).find();
249    }
250}