1 //////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code for adherence to a set of rules. 3 // Copyright (C) 2001-2019 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 //////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks; 21 22 import java.util.HashSet; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Set; 26 import java.util.regex.Pattern; 27 28 import com.puppycrawl.tools.checkstyle.StatelessCheck; 29 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 30 import com.puppycrawl.tools.checkstyle.api.DetailAST; 31 import com.puppycrawl.tools.checkstyle.api.TextBlock; 32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 33 34 /** 35 * <p> 36 * The check to ensure that comments are the only thing on a line. 37 * For the case of {@code //} comments that means that the only thing that should 38 * precede it is whitespace. 39 * It doesn't check comments if they do not end line, i.e. it accept 40 * the following: 41 * </p> 42 * <pre><code>Thread.sleep( 10 /*some comment here*/ );</code></pre> 43 * <p>Format property is intended to deal with the <code>} // while</code> example. 44 * </p> 45 * 46 * <p>Rationale: Steve McConnell in "Code Complete" suggests that endline 47 * comments are a bad practice. An end line comment would 48 * be one that is on the same line as actual code. For example: 49 * <pre> 50 * a = b + c; // Some insightful comment 51 * d = e / f; // Another comment for this line 52 * </pre> 53 * Quoting "Code Complete" for the justification: 54 * <ul> 55 * <li> 56 * "The comments have to be aligned so that they do not 57 * interfere with the visual structure of the code. If you don't 58 * align them neatly, they'll make your listing look like it's been 59 * through a washing machine." 60 * </li> 61 * <li> 62 * "Endline comments tend to be hard to format...It takes time 63 * to align them. Such time is not spent learning more about 64 * the code; it's dedicated solely to the tedious task of 65 * pressing the spacebar or tab key." 66 * </li> 67 * <li> 68 * "Endline comments are also hard to maintain. If the code on 69 * any line containing an endline comment grows, it bumps the 70 * comment farther out, and all the other endline comments will 71 * have to bumped out to match. Styles that are hard to 72 * maintain aren't maintained...." 73 * </li> 74 * <li> 75 * "Endline comments also tend to be cryptic. The right side of 76 * the line doesn't offer much room and the desire to keep the 77 * comment on one line means the comment must be short. 78 * Work then goes into making the line as short as possible 79 * instead of as clear as possible. The comment usually ends 80 * up as cryptic as possible...." 81 * </li> 82 * <li> 83 * "A systemic problem with endline comments is that it's hard 84 * to write a meaningful comment for one line of code. Most 85 * endline comments just repeat the line of code, which hurts 86 * more than it helps." 87 * </li> 88 * </ul> 89 * His comments on being hard to maintain when the size of 90 * the line changes are even more important in the age of 91 * automated refactorings. 92 * 93 * <p>To configure the check so it enforces only comment on a line: 94 * <pre> 95 * <module name="TrailingComment"> 96 * <property name="format" value="^\\s*$"/> 97 * </module> 98 * </pre> 99 * 100 * @noinspection HtmlTagCanBeJavadocTag 101 */ 102 @StatelessCheck 103 public class TrailingCommentCheck extends AbstractCheck { 104 105 /** 106 * A key is pointing to the warning message text in "messages.properties" 107 * file. 108 */ 109 public static final String MSG_KEY = "trailing.comments"; 110 111 /** Pattern for legal trailing comment. */ 112 private Pattern legalComment; 113 114 /** The regexp to match against. */ 115 private Pattern format = Pattern.compile("^[\\s});]*$"); 116 117 /** 118 * Sets patter for legal trailing comments. 119 * @param legalComment pattern to set. 120 */ 121 public void setLegalComment(final Pattern legalComment) { 122 this.legalComment = legalComment; 123 } 124 125 /** 126 * Set the format for the specified regular expression. 127 * @param pattern a pattern 128 */ 129 public final void setFormat(Pattern pattern) { 130 format = pattern; 131 } 132 133 @Override 134 public int[] getDefaultTokens() { 135 return getRequiredTokens(); 136 } 137 138 @Override 139 public int[] getAcceptableTokens() { 140 return getRequiredTokens(); 141 } 142 143 @Override 144 public int[] getRequiredTokens() { 145 return CommonUtil.EMPTY_INT_ARRAY; 146 } 147 148 @Override 149 public void visitToken(DetailAST ast) { 150 throw new IllegalStateException("visitToken() shouldn't be called."); 151 } 152 153 @Override 154 public void beginTree(DetailAST rootAST) { 155 final Map<Integer, TextBlock> cppComments = getFileContents() 156 .getSingleLineComments(); 157 final Map<Integer, List<TextBlock>> cComments = getFileContents() 158 .getBlockComments(); 159 final Set<Integer> lines = new HashSet<>(); 160 lines.addAll(cppComments.keySet()); 161 lines.addAll(cComments.keySet()); 162 163 for (Integer lineNo : lines) { 164 final String line = getLines()[lineNo - 1]; 165 final String lineBefore; 166 final TextBlock comment; 167 if (cppComments.containsKey(lineNo)) { 168 comment = cppComments.get(lineNo); 169 lineBefore = line.substring(0, comment.getStartColNo()); 170 } 171 else { 172 final List<TextBlock> commentList = cComments.get(lineNo); 173 comment = commentList.get(commentList.size() - 1); 174 lineBefore = line.substring(0, comment.getStartColNo()); 175 176 // do not check comment which doesn't end line 177 if (comment.getText().length == 1 178 && !CommonUtil.isBlank(line 179 .substring(comment.getEndColNo() + 1))) { 180 continue; 181 } 182 } 183 if (!format.matcher(lineBefore).find() 184 && !isLegalComment(comment)) { 185 log(lineNo, MSG_KEY); 186 } 187 } 188 } 189 190 /** 191 * Checks if given comment is legal (single-line and matches to the 192 * pattern). 193 * @param comment comment to check. 194 * @return true if the comment if legal. 195 */ 196 private boolean isLegalComment(final TextBlock comment) { 197 final boolean legal; 198 199 // multi-line comment can not be legal 200 if (legalComment == null || comment.getStartLineNo() != comment.getEndLineNo()) { 201 legal = false; 202 } 203 else { 204 String commentText = comment.getText()[0]; 205 // remove chars which start comment 206 commentText = commentText.substring(2); 207 // if this is a C-style comment we need to remove its end 208 if (commentText.endsWith("*/")) { 209 commentText = commentText.substring(0, commentText.length() - 2); 210 } 211 commentText = commentText.trim(); 212 legal = legalComment.matcher(commentText).find(); 213 } 214 return legal; 215 } 216 217 }