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