001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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*/ );</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. 093 * Type is {@code java.util.regex.Pattern}. 094 * Default value is {@code null}. 095 * </li> 096 * </ul> 097 * <p> 098 * To configure the check: 099 * </p> 100 * <pre> 101 * <module name="TrailingComment"/> 102 * </pre> 103 * <p> 104 * To configure the check so it enforces only comment on a line: 105 * </p> 106 * <pre> 107 * <module name="TrailingComment"> 108 * <property name="format" value="^\\s*$"/> 109 * </module> 110 * </pre> 111 * <p> 112 * Example for trailing comments check to suppress specific trailing comment: 113 * </p> 114 * <pre> 115 * public class Test { 116 * int a; // SUPPRESS CHECKSTYLE 117 * int b; // NOPMD 118 * int c; // NOSONAR 119 * int d; // violation, not suppressed 120 * } 121 * </pre> 122 * <p> 123 * To configure check so that trailing comment with exact comments like "SUPPRESS CHECKSTYLE", 124 * "NOPMD", "NOSONAR" are suppressed: 125 * </p> 126 * <pre> 127 * <module name="TrailingComment"/> 128 * <module name="SuppressionXpathSingleFilter"> 129 * <property name="checks" value="TrailingCommentCheck"/> 130 * <property name="query" value="//SINGLE_LINE_COMMENT 131 * [./COMMENT_CONTENT[@text=' NOSONAR\n' or @text=' NOPMD\n' 132 * or @text=' SUPPRESS CHECKSTYLE\n']]"/> 133 * </module> 134 * </pre> 135 * <p> 136 * To configure check so that trailing comment starting with "SUPPRESS CHECKSTYLE", "NOPMD", 137 * "NOSONAR" are suppressed: 138 * </p> 139 * <pre> 140 * <module name="TrailingComment"/> <module name="SuppressionXpathSingleFilter"> 141 * <property name="checks" value="TrailingCommentCheck"/> 142 * <property name="query" value="//SINGLE_LINE_COMMENT 143 * [./COMMENT_CONTENT[starts-with(@text, ' NOPMD')]]"/> 144 * <property name="query" value="//SINGLE_LINE_COMMENT 145 * [./COMMENT_CONTENT[starts-with(@text, ' SUPPRESS CHECKSTYLE')]]"/> 146 * <property name="query" value="//SINGLE_LINE_COMMENT 147 * [./COMMENT_CONTENT[starts-with(@text, ' NOSONAR')]]"/> 148 * </module> 149 * </pre> 150 * <p> 151 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 152 * </p> 153 * <p> 154 * Violation Message Keys: 155 * </p> 156 * <ul> 157 * <li> 158 * {@code trailing.comments} 159 * </li> 160 * </ul> 161 * 162 * @noinspection HtmlTagCanBeJavadocTag 163 * @since 3.4 164 */ 165@StatelessCheck 166public class TrailingCommentCheck extends AbstractCheck { 167 168 /** 169 * A key is pointing to the warning message text in "messages.properties" 170 * file. 171 */ 172 public static final String MSG_KEY = "trailing.comments"; 173 174 /** Specify pattern for strings to be formatted without comment specifiers. */ 175 private static final Pattern FORMAT_LINE = Pattern.compile("/"); 176 177 /** 178 * Define pattern for text allowed in trailing comments. 179 * This pattern will not be applied to multiline comments. 180 */ 181 private Pattern legalComment; 182 183 /** Specify pattern for strings allowed before the comment. */ 184 private Pattern format = Pattern.compile("^[\\s});]*$"); 185 186 /** 187 * Setter to define pattern for text allowed in trailing comments. 188 * This pattern will not be applied to multiline comments. 189 * 190 * @param legalComment pattern to set. 191 */ 192 public void setLegalComment(final Pattern legalComment) { 193 this.legalComment = legalComment; 194 } 195 196 /** 197 * Setter to specify pattern for strings allowed before the comment. 198 * 199 * @param pattern a pattern 200 */ 201 public final void setFormat(Pattern pattern) { 202 format = pattern; 203 } 204 205 @Override 206 public boolean isCommentNodesRequired() { 207 return true; 208 } 209 210 @Override 211 public int[] getDefaultTokens() { 212 return getRequiredTokens(); 213 } 214 215 @Override 216 public int[] getAcceptableTokens() { 217 return getRequiredTokens(); 218 } 219 220 @Override 221 public int[] getRequiredTokens() { 222 return new int[] { 223 TokenTypes.SINGLE_LINE_COMMENT, 224 TokenTypes.BLOCK_COMMENT_BEGIN, 225 }; 226 } 227 228 @Override 229 public void visitToken(DetailAST ast) { 230 if (ast.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 231 checkSingleLineComment(ast); 232 } 233 else { 234 checkBlockComment(ast); 235 } 236 } 237 238 /** 239 * Checks if single line comment is legal. 240 * 241 * @param ast Detail ast element to be checked. 242 */ 243 private void checkSingleLineComment(DetailAST ast) { 244 final int lineNo = ast.getLineNo(); 245 final String comment = ast.getFirstChild().getText(); 246 final String line = getLines()[lineNo - 1]; 247 final String lineBefore = line.substring(0, ast.getColumnNo()); 248 249 if (!format.matcher(lineBefore).find() && !isLegalCommentContent(comment)) { 250 log(ast, MSG_KEY); 251 } 252 } 253 254 /** 255 * Method to check if block comment is in correct format. 256 * 257 * @param ast Detail ast element to be checked. 258 */ 259 private void checkBlockComment(DetailAST ast) { 260 final int lineNo = ast.getLineNo(); 261 final DetailAST firstChild = ast.getFirstChild(); 262 final DetailAST lastChild = ast.getLastChild(); 263 final String comment = firstChild.getText(); 264 String line = getLines()[lineNo - 1]; 265 266 if (line.length() > lastChild.getColumnNo() + 1) { 267 line = line.substring(lastChild.getColumnNo() + 2); 268 } 269 270 line = FORMAT_LINE.matcher(line).replaceAll(""); 271 272 final String lineBefore = getLines()[lineNo - 1].substring(0, ast.getColumnNo()); 273 final boolean isCommentAtEndOfLine = ast.getLineNo() != lastChild.getLineNo() 274 || CommonUtil.isBlank(line); 275 final boolean isLegalBlockComment = isLegalCommentContent(comment) 276 && TokenUtil.areOnSameLine(firstChild, lastChild) 277 || format.matcher(lineBefore).find(); 278 279 if (isCommentAtEndOfLine && !isLegalBlockComment) { 280 log(ast, MSG_KEY); 281 } 282 } 283 284 /** 285 * Checks if given comment content is legal. 286 * 287 * @param commentContent comment content to check. 288 * @return true if the content is legal. 289 */ 290 private boolean isLegalCommentContent(String commentContent) { 291 return legalComment != null && legalComment.matcher(commentContent).find(); 292 } 293}