View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.CommonUtils;
33  
34  /**
35   * <p>
36   * The check to ensure that comments are the only thing on a line.
37   * For the case of // 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   * {@code Thread.sleep( 10 &lt;some comment here&gt; );}
42   * Format property is intended to deal with the "} // while" example.
43   * </p>
44   *
45   * <p>Rationale: Steve McConnell in &quot;Code Complete&quot; suggests that endline
46   * comments are a bad practice. An end line comment would
47   * be one that is on the same line as actual code. For example:
48   * <pre>
49   *  a = b + c;      // Some insightful comment
50   *  d = e / f;        // Another comment for this line
51   * </pre>
52   * Quoting &quot;Code Complete&quot; for the justification:
53   * <ul>
54   * <li>
55   * &quot;The comments have to be aligned so that they do not
56   * interfere with the visual structure of the code. If you don't
57   * align them neatly, they'll make your listing look like it's been
58   * through a washing machine.&quot;
59   * </li>
60   * <li>
61   * &quot;Endline comments tend to be hard to format...It takes time
62   * to align them. Such time is not spent learning more about
63   * the code; it's dedicated solely to the tedious task of
64   * pressing the spacebar or tab key.&quot;
65   * </li>
66   * <li>
67   * &quot;Endline comments are also hard to maintain. If the code on
68   * any line containing an endline comment grows, it bumps the
69   * comment farther out, and all the other endline comments will
70   * have to bumped out to match. Styles that are hard to
71   * maintain aren't maintained....&quot;
72   * </li>
73   * <li>
74   * &quot;Endline comments also tend to be cryptic. The right side of
75   * the line doesn't offer much room and the desire to keep the
76   * comment on one line means the comment must be short.
77   * Work then goes into making the line as short as possible
78   * instead of as clear as possible. The comment usually ends
79   * up as cryptic as possible....&quot;
80   * </li>
81   * <li>
82   * &quot;A systemic problem with endline comments is that it's hard
83   * to write a meaningful comment for one line of code. Most
84   * endline comments just repeat the line of code, which hurts
85   * more than it helps.&quot;
86   * </li>
87   * </ul>
88   * His comments on being hard to maintain when the size of
89   * the line changes are even more important in the age of
90   * automated refactorings.
91   *
92   * <p>To configure the check so it enforces only comment on a line:
93   * <pre>
94   * &lt;module name=&quot;TrailingComment&quot;&gt;
95   *    &lt;property name=&quot;format&quot; value=&quot;^\\s*$&quot;/&gt;
96   * &lt;/module&gt;
97   * </pre>
98   *
99   * @author o_sukhodolsky
100  */
101 @StatelessCheck
102 public class TrailingCommentCheck extends AbstractCheck {
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 regexp to match against. */
114     private Pattern format = Pattern.compile("^[\\s});]*$");
115 
116     /**
117      * Sets patter for legal trailing comments.
118      * @param legalComment pattern to set.
119      */
120     public void setLegalComment(final Pattern legalComment) {
121         this.legalComment = legalComment;
122     }
123 
124     /**
125      * Set the format for the specified regular expression.
126      * @param pattern a pattern
127      */
128     public final void setFormat(Pattern pattern) {
129         format = pattern;
130     }
131 
132     @Override
133     public int[] getDefaultTokens() {
134         return getRequiredTokens();
135     }
136 
137     @Override
138     public int[] getAcceptableTokens() {
139         return getRequiredTokens();
140     }
141 
142     @Override
143     public int[] getRequiredTokens() {
144         return CommonUtils.EMPTY_INT_ARRAY;
145     }
146 
147     @Override
148     public void visitToken(DetailAST ast) {
149         throw new IllegalStateException("visitToken() shouldn't be called.");
150     }
151 
152     @Override
153     public void beginTree(DetailAST rootAST) {
154         final Map<Integer, TextBlock> cppComments = getFileContents()
155                 .getSingleLineComments();
156         final Map<Integer, List<TextBlock>> cComments = getFileContents()
157                 .getBlockComments();
158         final Set<Integer> lines = new HashSet<>();
159         lines.addAll(cppComments.keySet());
160         lines.addAll(cComments.keySet());
161 
162         for (Integer lineNo : lines) {
163             final String line = getLines()[lineNo - 1];
164             final String lineBefore;
165             final TextBlock comment;
166             if (cppComments.containsKey(lineNo)) {
167                 comment = cppComments.get(lineNo);
168                 lineBefore = line.substring(0, comment.getStartColNo());
169             }
170             else {
171                 final List<TextBlock> commentList = cComments.get(lineNo);
172                 comment = commentList.get(commentList.size() - 1);
173                 lineBefore = line.substring(0, comment.getStartColNo());
174 
175                 // do not check comment which doesn't end line
176                 if (comment.getText().length == 1
177                         && !CommonUtils.isBlank(line
178                             .substring(comment.getEndColNo() + 1))) {
179                     continue;
180                 }
181             }
182             if (!format.matcher(lineBefore).find()
183                 && !isLegalComment(comment)) {
184                 log(lineNo, MSG_KEY);
185             }
186         }
187     }
188 
189     /**
190      * Checks if given comment is legal (single-line and matches to the
191      * pattern).
192      * @param comment comment to check.
193      * @return true if the comment if legal.
194      */
195     private boolean isLegalComment(final TextBlock comment) {
196         final boolean legal;
197 
198         // multi-line comment can not be legal
199         if (legalComment == null || comment.getStartLineNo() != comment.getEndLineNo()) {
200             legal = false;
201         }
202         else {
203             String commentText = comment.getText()[0];
204             // remove chars which start comment
205             commentText = commentText.substring(2);
206             // if this is a C-style comment we need to remove its end
207             if (commentText.endsWith("*/")) {
208                 commentText = commentText.substring(0, commentText.length() - 2);
209             }
210             commentText = commentText.trim();
211             legal = legalComment.matcher(commentText).find();
212         }
213         return legal;
214     }
215 }