View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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 {@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&#42;/ );</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 &quot;Code Complete&quot; 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 &quot;Code Complete&quot; for the justification:
54   * <ul>
55   * <li>
56   * &quot;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.&quot;
60   * </li>
61   * <li>
62   * &quot;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.&quot;
66   * </li>
67   * <li>
68   * &quot;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....&quot;
73   * </li>
74   * <li>
75   * &quot;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....&quot;
81   * </li>
82   * <li>
83   * &quot;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.&quot;
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   * &lt;module name=&quot;TrailingComment&quot;&gt;
96   *    &lt;property name=&quot;format&quot; value=&quot;^\\s*$&quot;/&gt;
97   * &lt;/module&gt;
98   * </pre>
99   *
100  * @author o_sukhodolsky
101  * @noinspection HtmlTagCanBeJavadocTag
102  */
103 @StatelessCheck
104 public class TrailingCommentCheck extends AbstractCheck {
105 
106     /**
107      * A key is pointing to the warning message text in "messages.properties"
108      * file.
109      */
110     public static final String MSG_KEY = "trailing.comments";
111 
112     /** Pattern for legal trailing comment. */
113     private Pattern legalComment;
114 
115     /** The regexp to match against. */
116     private Pattern format = Pattern.compile("^[\\s});]*$");
117 
118     /**
119      * Sets patter for legal trailing comments.
120      * @param legalComment pattern to set.
121      */
122     public void setLegalComment(final Pattern legalComment) {
123         this.legalComment = legalComment;
124     }
125 
126     /**
127      * Set the format for the specified regular expression.
128      * @param pattern a pattern
129      */
130     public final void setFormat(Pattern pattern) {
131         format = pattern;
132     }
133 
134     @Override
135     public int[] getDefaultTokens() {
136         return getRequiredTokens();
137     }
138 
139     @Override
140     public int[] getAcceptableTokens() {
141         return getRequiredTokens();
142     }
143 
144     @Override
145     public int[] getRequiredTokens() {
146         return CommonUtils.EMPTY_INT_ARRAY;
147     }
148 
149     @Override
150     public void visitToken(DetailAST ast) {
151         throw new IllegalStateException("visitToken() shouldn't be called.");
152     }
153 
154     @Override
155     public void beginTree(DetailAST rootAST) {
156         final Map<Integer, TextBlock> cppComments = getFileContents()
157                 .getSingleLineComments();
158         final Map<Integer, List<TextBlock>> cComments = getFileContents()
159                 .getBlockComments();
160         final Set<Integer> lines = new HashSet<>();
161         lines.addAll(cppComments.keySet());
162         lines.addAll(cComments.keySet());
163 
164         for (Integer lineNo : lines) {
165             final String line = getLines()[lineNo - 1];
166             final String lineBefore;
167             final TextBlock comment;
168             if (cppComments.containsKey(lineNo)) {
169                 comment = cppComments.get(lineNo);
170                 lineBefore = line.substring(0, comment.getStartColNo());
171             }
172             else {
173                 final List<TextBlock> commentList = cComments.get(lineNo);
174                 comment = commentList.get(commentList.size() - 1);
175                 lineBefore = line.substring(0, comment.getStartColNo());
176 
177                 // do not check comment which doesn't end line
178                 if (comment.getText().length == 1
179                         && !CommonUtils.isBlank(line
180                             .substring(comment.getEndColNo() + 1))) {
181                     continue;
182                 }
183             }
184             if (!format.matcher(lineBefore).find()
185                 && !isLegalComment(comment)) {
186                 log(lineNo, MSG_KEY);
187             }
188         }
189     }
190 
191     /**
192      * Checks if given comment is legal (single-line and matches to the
193      * pattern).
194      * @param comment comment to check.
195      * @return true if the comment if legal.
196      */
197     private boolean isLegalComment(final TextBlock comment) {
198         final boolean legal;
199 
200         // multi-line comment can not be legal
201         if (legalComment == null || comment.getStartLineNo() != comment.getEndLineNo()) {
202             legal = false;
203         }
204         else {
205             String commentText = comment.getText()[0];
206             // remove chars which start comment
207             commentText = commentText.substring(2);
208             // if this is a C-style comment we need to remove its end
209             if (commentText.endsWith("*/")) {
210                 commentText = commentText.substring(0, commentText.length() - 2);
211             }
212             commentText = commentText.trim();
213             legal = legalComment.matcher(commentText).find();
214         }
215         return legal;
216     }
217 
218 }