001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.regex.Pattern; 026 027import org.apache.commons.lang3.ArrayUtils; 028 029import com.google.common.collect.Sets; 030import com.puppycrawl.tools.checkstyle.api.Check; 031import com.puppycrawl.tools.checkstyle.api.DetailAST; 032import com.puppycrawl.tools.checkstyle.api.TextBlock; 033import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 034 035/** 036 * <p> 037 * The check to ensure that requires that comments be the only thing on a line. 038 * For the case of // comments that means that the only thing that should 039 * precede it is whitespace. 040 * It doesn't check comments if they do not end line, i.e. it accept 041 * the following: 042 * {@code Thread.sleep( 10 <some comment here> );} 043 * Format property is intended to deal with the "} // while" example. 044 * </p> 045 * 046 * <p>Rationale: Steve McConnel in "Code Complete" suggests that endline 047 * comments are a bad practice. An end line comment would 048 * be one that is on the same line as actual code. For example: 049 * <pre> 050 * a = b + c; // Some insightful comment 051 * d = e / f; // Another comment for this line 052 * </pre> 053 * Quoting "Code Complete" for the justification: 054 * <ul> 055 * <li> 056 * "The comments have to be aligned so that they do not 057 * interfere with the visual structure of the code. If you don't 058 * align them neatly, they'll make your listing look like it's been 059 * through a washing machine." 060 * </li> 061 * <li> 062 * "Endline comments tend to be hard to format...It takes time 063 * to align them. Such time is not spent learning more about 064 * the code; it's dedicated solely to the tedious task of 065 * pressing the spacebar or tab key." 066 * </li> 067 * <li> 068 * "Endline comments are also hard to maintain. If the code on 069 * any line containing an endline comment grows, it bumps the 070 * comment farther out, and all the other endline comments will 071 * have to bumped out to match. Styles that are hard to 072 * maintain aren't maintained...." 073 * </li> 074 * <li> 075 * "Endline comments also tend to be cryptic. The right side of 076 * the line doesn't offer much room and the desire to keep the 077 * comment on one line means the comment must be short. 078 * Work then goes into making the line as short as possible 079 * instead of as clear as possible. The comment usually ends 080 * up as cryptic as possible...." 081 * </li> 082 * <li> 083 * "A systemic problem with endline comments is that it's hard 084 * to write a meaningful comment for one line of code. Most 085 * endline comments just repeat the line of code, which hurts 086 * more than it helps." 087 * </li> 088 * </ul> 089 * His comments on being hard to maintain when the size of 090 * the line changes are even more important in the age of 091 * automated refactorings. 092 * 093 * <p>To configure the check so it enforces only comment on a line: 094 * <pre> 095 * <module name="TrailingComment"> 096 * <property name="format" value="^\\s*$"/> 097 * </module> 098 * </pre> 099 * 100 * @author o_sukhodolsky 101 */ 102public class TrailingCommentCheck extends Check { 103 104 /** 105 * A key is pointing to the warning message text in "messages.properties" 106 * file. 107 */ 108 public static final String MSG_KEY = "trailing.comments"; 109 110 /** Pattern for legal trailing comment. */ 111 private Pattern legalComment; 112 113 /** The format string of the regexp. */ 114 private String format = "^[\\s\\}\\);]*$"; 115 116 /** The regexp to match against. */ 117 private Pattern regexp = Pattern.compile(format); 118 119 /** 120 * Sets patter for legal trailing comments. 121 * @param legalComment format to set. 122 */ 123 public void setLegalComment(final String legalComment) { 124 this.legalComment = CommonUtils.createPattern(legalComment); 125 } 126 127 /** 128 * Set the format to the specified regular expression. 129 * @param format a {@code String} value 130 * @throws org.apache.commons.beanutils.ConversionException unable to parse format 131 */ 132 public final void setFormat(String format) { 133 this.format = format; 134 regexp = CommonUtils.createPattern(format); 135 } 136 137 @Override 138 public int[] getDefaultTokens() { 139 return ArrayUtils.EMPTY_INT_ARRAY; 140 } 141 142 @Override 143 public int[] getAcceptableTokens() { 144 return ArrayUtils.EMPTY_INT_ARRAY; 145 } 146 147 @Override 148 public int[] getRequiredTokens() { 149 return ArrayUtils.EMPTY_INT_ARRAY; 150 } 151 152 @Override 153 public void visitToken(DetailAST ast) { 154 throw new IllegalStateException("visitToken() shouldn't be called."); 155 } 156 157 @Override 158 public void beginTree(DetailAST rootAST) { 159 final Map<Integer, TextBlock> cppComments = getFileContents() 160 .getCppComments(); 161 final Map<Integer, List<TextBlock>> cComments = getFileContents() 162 .getCComments(); 163 final Set<Integer> lines = Sets.newHashSet(); 164 lines.addAll(cppComments.keySet()); 165 lines.addAll(cComments.keySet()); 166 167 for (Integer lineNo : lines) { 168 final String line = getLines()[lineNo - 1]; 169 String lineBefore; 170 TextBlock comment; 171 if (cppComments.containsKey(lineNo)) { 172 comment = cppComments.get(lineNo); 173 lineBefore = line.substring(0, comment.getStartColNo()); 174 } 175 else { 176 final List<TextBlock> commentList = cComments.get(lineNo); 177 comment = commentList.get(commentList.size() - 1); 178 lineBefore = line.substring(0, comment.getStartColNo()); 179 180 // do not check comment which doesn't end line 181 if (comment.getText().length == 1 182 && !line.substring(comment.getEndColNo() + 1).trim().isEmpty()) { 183 continue; 184 } 185 } 186 if (!regexp.matcher(lineBefore).find() 187 && !isLegalComment(comment)) { 188 log(lineNo, MSG_KEY); 189 } 190 } 191 } 192 193 /** 194 * Checks if given comment is legal (single-line and matches to the 195 * pattern). 196 * @param comment comment to check. 197 * @return true if the comment if legal. 198 */ 199 private boolean isLegalComment(final TextBlock comment) { 200 boolean legal; 201 202 // multi-line comment can not be legal 203 if (legalComment == null || comment.getStartLineNo() != comment.getEndLineNo()) { 204 legal = false; 205 } 206 else { 207 String commentText = comment.getText()[0]; 208 // remove chars which start comment 209 commentText = commentText.substring(2); 210 // if this is a C-style comment we need to remove its end 211 if (commentText.endsWith("*/")) { 212 commentText = commentText.substring(0, commentText.length() - 2); 213 } 214 commentText = commentText.trim(); 215 legal = legalComment.matcher(commentText).find(); 216 } 217 return legal; 218 } 219}