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