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