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.whitespace;
21  
22  import com.puppycrawl.tools.checkstyle.StatelessCheck;
23  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
24  import com.puppycrawl.tools.checkstyle.api.DetailAST;
25  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
26  
27  /**
28   * <p>
29   * Checks that non-whitespace characters are separated by no more than one
30   * whitespace. Separating characters by tabs or multiple spaces will be
31   * reported. Currently the check doesn't permit horizontal alignment. To inspect
32   * whitespaces before and after comments, set the property
33   * <b>validateComments</b> to true.
34   * </p>
35   *
36   * <p>
37   * Setting <b>validateComments</b> to false will ignore cases like:
38   * </p>
39   *
40   * <pre>
41   * int i;  &#47;&#47; Multiple whitespaces before comment tokens will be ignored.
42   * private void foo(int  &#47;* whitespaces before and after block-comments will be
43   * ignored *&#47;  i) {
44   * </pre>
45   *
46   * <p>
47   * Sometimes, users like to space similar items on different lines to the same
48   * column position for easier reading. This feature isn't supported by this
49   * check, so both braces in the following case will be reported as violations.
50   * </p>
51   *
52   * <pre>
53   * public long toNanos(long d)  { return d;             }  &#47;&#47; 2 violations
54   * public long toMicros(long d) { return d / (C1 / C0); }
55   * </pre>
56   *
57   * <p>
58   * Check have following options:
59   * </p>
60   *
61   * <ul>
62   * <li>validateComments - Boolean when set to {@code true}, whitespaces
63   * surrounding comments will be ignored. Default value is {@code false}.</li>
64   * </ul>
65   *
66   * <p>
67   * To configure the check:
68   * </p>
69   *
70   * <pre>
71   * &lt;module name=&quot;SingleSpaceSeparator&quot;/&gt;
72   * </pre>
73   *
74   * <p>
75   * To configure the check so that it validates comments:
76   * </p>
77   *
78   * <pre>
79   * &lt;module name=&quot;SingleSpaceSeparator&quot;&gt;
80   * &lt;property name=&quot;validateComments&quot; value=&quot;true&quot;/&gt;
81   * &lt;/module&gt;
82   * </pre>
83   *
84   * @author Robert Whitebit
85   * @author Richard Veach
86   */
87  @StatelessCheck
88  public class SingleSpaceSeparatorCheck extends AbstractCheck {
89      /**
90       * A key is pointing to the warning message text in "messages.properties"
91       * file.
92       */
93      public static final String MSG_KEY = "single.space.separator";
94  
95      /** Indicates if whitespaces surrounding comments will be ignored. */
96      private boolean validateComments;
97  
98      /**
99       * Sets whether or not to validate surrounding whitespaces at comments.
100      *
101      * @param validateComments {@code true} to validate surrounding whitespaces at comments.
102      */
103     public void setValidateComments(boolean validateComments) {
104         this.validateComments = validateComments;
105     }
106 
107     @Override
108     public int[] getDefaultTokens() {
109         return getRequiredTokens();
110     }
111 
112     @Override
113     public int[] getAcceptableTokens() {
114         return getRequiredTokens();
115     }
116 
117     @Override
118     public int[] getRequiredTokens() {
119         return CommonUtils.EMPTY_INT_ARRAY;
120     }
121 
122     // -@cs[SimpleAccessorNameNotation] Overrides method from base class.
123     // Issue: https://github.com/sevntu-checkstyle/sevntu.checkstyle/issues/166
124     @Override
125     public boolean isCommentNodesRequired() {
126         return validateComments;
127     }
128 
129     @Override
130     public void beginTree(DetailAST rootAST) {
131         visitEachToken(rootAST);
132     }
133 
134     /**
135      * Examines every sibling and child of {@code node} for violations.
136      *
137      * @param node The node to start examining.
138      */
139     private void visitEachToken(DetailAST node) {
140         DetailAST sibling = node;
141 
142         while (sibling != null) {
143             final int columnNo = sibling.getColumnNo() - 1;
144 
145             // in such expression: "j  =123", placed at the start of the string index of the second
146             // space character will be: 2 = 0(j) + 1(whitespace) + 1(whitespace). It is a minimal
147             // possible index for the second whitespace between non-whitespace characters.
148             final int minSecondWhitespaceColumnNo = 2;
149 
150             if (columnNo >= minSecondWhitespaceColumnNo
151                     && !isTextSeparatedCorrectlyFromPrevious(getLine(sibling.getLineNo() - 1),
152                             columnNo)) {
153                 log(sibling.getLineNo(), columnNo, MSG_KEY);
154             }
155             if (sibling.getChildCount() >= 1) {
156                 visitEachToken(sibling.getFirstChild());
157             }
158 
159             sibling = sibling.getNextSibling();
160         }
161     }
162 
163     /**
164      * Checks if characters in {@code line} at and around {@code columnNo} has
165      * the correct number of spaces. to return {@code true} the following
166      * conditions must be met:<br />
167      * - the character at {@code columnNo} is the first in the line.<br />
168      * - the character at {@code columnNo} is not separated by whitespaces from
169      * the previous non-whitespace character. <br />
170      * - the character at {@code columnNo} is separated by only one whitespace
171      * from the previous non-whitespace character.<br />
172      * - {@link #validateComments} is disabled and the previous text is the
173      * end of a block comment.
174      *
175      * @param line The line in the file to examine.
176      * @param columnNo The column position in the {@code line} to examine.
177      * @return {@code true} if the text at {@code columnNo} is separated
178      *         correctly from the previous token.
179      */
180     private boolean isTextSeparatedCorrectlyFromPrevious(String line, int columnNo) {
181         return isSingleSpace(line, columnNo)
182                 || !isWhitespace(line, columnNo)
183                 || isFirstInLine(line, columnNo)
184                 || !validateComments && isBlockCommentEnd(line, columnNo);
185     }
186 
187     /**
188      * Checks if the {@code line} at {@code columnNo} is a single space, and not
189      * preceded by another space.
190      *
191      * @param line The line in the file to examine.
192      * @param columnNo The column position in the {@code line} to examine.
193      * @return {@code true} if the character at {@code columnNo} is a space, and
194      *         not preceded by another space.
195      */
196     private static boolean isSingleSpace(String line, int columnNo) {
197         return !isPrecededByMultipleWhitespaces(line, columnNo)
198                 && isSpace(line, columnNo);
199     }
200 
201     /**
202      * Checks if the {@code line} at {@code columnNo} is a space.
203      *
204      * @param line The line in the file to examine.
205      * @param columnNo The column position in the {@code line} to examine.
206      * @return {@code true} if the character at {@code columnNo} is a space.
207      */
208     private static boolean isSpace(String line, int columnNo) {
209         return line.charAt(columnNo) == ' ';
210     }
211 
212     /**
213      * Checks if the {@code line} at {@code columnNo} is preceded by at least 2
214      * whitespaces.
215      *
216      * @param line The line in the file to examine.
217      * @param columnNo The column position in the {@code line} to examine.
218      * @return {@code true} if there are at least 2 whitespace characters before
219      *         {@code columnNo}.
220      */
221     private static boolean isPrecededByMultipleWhitespaces(String line, int columnNo) {
222         return Character.isWhitespace(line.charAt(columnNo))
223                 && Character.isWhitespace(line.charAt(columnNo - 1));
224     }
225 
226     /**
227      * Checks if the {@code line} at {@code columnNo} is a whitespace character.
228      *
229      * @param line The line in the file to examine.
230      * @param columnNo The column position in the {@code line} to examine.
231      * @return {@code true} if the character at {@code columnNo} is a
232      *         whitespace.
233      */
234     private static boolean isWhitespace(String line, int columnNo) {
235         return Character.isWhitespace(line.charAt(columnNo));
236     }
237 
238     /**
239      * Checks if the {@code line} up to and including {@code columnNo} is all
240      * non-whitespace text encountered.
241      *
242      * @param line The line in the file to examine.
243      * @param columnNo The column position in the {@code line} to examine.
244      * @return {@code true} if the column position is the first non-whitespace
245      *         text on the {@code line}.
246      */
247     private static boolean isFirstInLine(String line, int columnNo) {
248         return CommonUtils.isBlank(line.substring(0, columnNo));
249     }
250 
251     /**
252      * Checks if the {@code line} at {@code columnNo} is the end of a comment,
253      * '*&#47;'.
254      *
255      * @param line The line in the file to examine.
256      * @param columnNo The column position in the {@code line} to examine.
257      * @return {@code true} if the previous text is a end comment block.
258      */
259     private static boolean isBlockCommentEnd(String line, int columnNo) {
260         return line.substring(0, columnNo).trim().endsWith("*/");
261     }
262 }