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 * 113 * <p> 114 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 115 * </p> 116 * <p> 117 * Violation Message Keys: 118 * </p> 119 * <ul> 120 * <li> 121 * {@code trailing.comments} 122 * </li> 123 * </ul> 124 * 125 * @noinspection HtmlTagCanBeJavadocTag 126 * @since 3.4 127 */ 128@StatelessCheck 129public class TrailingCommentCheck extends AbstractCheck { 130 131 /** 132 * A key is pointing to the warning message text in "messages.properties" 133 * file. 134 */ 135 public static final String MSG_KEY = "trailing.comments"; 136 137 /** Specify pattern for strings to be formatted without comment specifiers. */ 138 private static final Pattern FORMAT_LINE = Pattern.compile("/"); 139 140 /** 141 * Define pattern for text allowed in trailing comments. 142 * (This pattern will not be applied to multiline comments and the text 143 * of the comment will be trimmed before matching.) 144 */ 145 private Pattern legalComment; 146 147 /** Specify pattern for strings allowed before the comment. */ 148 private Pattern format = Pattern.compile("^[\\s});]*$"); 149 150 /** 151 * Setter to define pattern for text allowed in trailing comments. 152 * (This pattern will not be applied to multiline comments and the text 153 * of the comment will be trimmed before matching.) 154 * 155 * @param legalComment pattern to set. 156 */ 157 public void setLegalComment(final Pattern legalComment) { 158 this.legalComment = legalComment; 159 } 160 161 /** 162 * Setter to specify pattern for strings allowed before the comment. 163 * 164 * @param pattern a pattern 165 */ 166 public final void setFormat(Pattern pattern) { 167 format = pattern; 168 } 169 170 @Override 171 public boolean isCommentNodesRequired() { 172 return true; 173 } 174 175 @Override 176 public int[] getDefaultTokens() { 177 return getRequiredTokens(); 178 } 179 180 @Override 181 public int[] getAcceptableTokens() { 182 return getRequiredTokens(); 183 } 184 185 @Override 186 public int[] getRequiredTokens() { 187 return new int[] { 188 TokenTypes.SINGLE_LINE_COMMENT, 189 TokenTypes.BLOCK_COMMENT_BEGIN, 190 }; 191 } 192 193 @Override 194 public void visitToken(DetailAST ast) { 195 if (ast.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 196 checkSingleLineComment(ast); 197 } 198 else { 199 checkBlockComment(ast); 200 } 201 } 202 203 /** 204 * Checks if single line comment is legal. 205 * 206 * @param ast Detail ast element to be checked. 207 */ 208 private void checkSingleLineComment(DetailAST ast) { 209 final int lineNo = ast.getLineNo(); 210 final String comment = ast.getFirstChild().getText(); 211 final String line = getLines()[lineNo - 1]; 212 final String lineBefore = line.substring(0, ast.getColumnNo()); 213 214 if (!format.matcher(lineBefore).find() 215 && !isLegalSingleLineComment(comment)) { 216 log(ast, MSG_KEY); 217 } 218 } 219 220 /** 221 * Method to check if block comment is in correct format. 222 * 223 * @param ast Detail ast element to be checked. 224 */ 225 private void checkBlockComment(DetailAST ast) { 226 final int lineNo = ast.getLineNo(); 227 final String comment = ast.getFirstChild().getText(); 228 String line = getLines()[lineNo - 1]; 229 230 if (line.length() > ast.getLastChild().getColumnNo() + 1) { 231 line = line.substring(ast.getLastChild().getColumnNo() + 2); 232 } 233 234 line = FORMAT_LINE.matcher(line).replaceAll(""); 235 236 final String lineBefore = getLines()[lineNo - 1].substring(0, ast.getColumnNo()); 237 238 // do not check comment which doesn't end line 239 if ((ast.getLineNo() != ast.getLastChild().getLineNo() || CommonUtil.isBlank(line)) 240 && !format.matcher(lineBefore).find() 241 && !isLegalBlockComment(ast, comment)) { 242 log(ast, MSG_KEY); 243 } 244 } 245 246 /** 247 * Checks if block comment is legal and matches to the pattern. 248 * 249 * @param ast Detail ast element to be checked. 250 * @param comment comment to check. 251 * @return true if the comment if legal. 252 */ 253 private boolean isLegalBlockComment(DetailAST ast, String comment) { 254 final boolean legal; 255 256 // multi-line comment can not be legal 257 if (legalComment == null 258 || !TokenUtil.areOnSameLine(ast.getFirstChild(), ast.getLastChild())) { 259 legal = false; 260 } 261 else { 262 final String commentText = comment.trim(); 263 legal = legalComment.matcher(commentText).find(); 264 } 265 return legal; 266 } 267 268 /** 269 * Checks if given single line comment is legal (single-line and matches to the 270 * pattern). 271 * 272 * @param comment comment to check. 273 * @return true if the comment if legal. 274 */ 275 private boolean isLegalSingleLineComment(String comment) { 276 final boolean legal; 277 if (legalComment == null) { 278 legal = false; 279 } 280 else { 281 // remove chars which start comment 282 final String commentText = comment.substring(1).trim(); 283 legal = legalComment.matcher(commentText).find(); 284 } 285 return legal; 286 } 287}