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