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