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.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.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
27  
28  /**
29   * <p>
30   * Checks that there is no whitespace after a token.
31   * More specifically, it checks that it is not followed by whitespace,
32   * or (if linebreaks are allowed) all characters on the line after are
33   * whitespace. To forbid linebreaks after a token, set property
34   * allowLineBreaks to false.
35   * </p>
36    * <p> By default the check will check the following operators:
37   *  {@link TokenTypes#ARRAY_INIT ARRAY_INIT},
38   *  {@link TokenTypes#AT AT},
39   *  {@link TokenTypes#BNOT BNOT},
40   *  {@link TokenTypes#DEC DEC},
41   *  {@link TokenTypes#DOT DOT},
42   *  {@link TokenTypes#INC INC},
43   *  {@link TokenTypes#LNOT LNOT},
44   *  {@link TokenTypes#UNARY_MINUS UNARY_MINUS},
45   *  {@link TokenTypes#UNARY_PLUS UNARY_PLUS},
46   *  {@link TokenTypes#TYPECAST TYPECAST},
47   *  {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
48   *  {@link TokenTypes#INDEX_OP INDEX_OP}.
49   * </p>
50   * <p>
51   * The check processes
52   * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR},
53   * {@link TokenTypes#INDEX_OP INDEX_OP}
54   * specially from other tokens. Actually it is checked that there is
55   * no whitespace before this tokens, not after them.
56   * Spaces after the {@link TokenTypes#ANNOTATIONS ANNOTATIONS}
57   * before {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}
58   * and {@link TokenTypes#INDEX_OP INDEX_OP} will be ignored.
59   * </p>
60   * <p>
61   * An example of how to configure the check is:
62   * </p>
63   * <pre>
64   * &lt;module name="NoWhitespaceAfter"/&gt;
65   * </pre>
66   * <p> An example of how to configure the check to forbid linebreaks after
67   * a {@link TokenTypes#DOT DOT} token is:
68   * </p>
69   * <pre>
70   * &lt;module name="NoWhitespaceAfter"&gt;
71   *     &lt;property name="tokens" value="DOT"/&gt;
72   *     &lt;property name="allowLineBreaks" value="false"/&gt;
73   * &lt;/module&gt;
74   * </pre>
75   * <p>
76   * If the annotation is between the type and the array, the check will skip validation for spaces:
77   * </p>
78   * <pre>
79   * public void foo(final char @NotNull [] param) {} // No violation
80   * </pre>
81   * @author Rick Giles
82   * @author lkuehne
83   * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
84   * @author attatrol
85   */
86  @StatelessCheck
87  public class NoWhitespaceAfterCheck extends AbstractCheck {
88  
89      /**
90       * A key is pointing to the warning message text in "messages.properties"
91       * file.
92       */
93      public static final String MSG_KEY = "ws.followed";
94  
95      /** Whether whitespace is allowed if the AST is at a linebreak. */
96      private boolean allowLineBreaks = true;
97  
98      @Override
99      public int[] getDefaultTokens() {
100         return new int[] {
101             TokenTypes.ARRAY_INIT,
102             TokenTypes.AT,
103             TokenTypes.INC,
104             TokenTypes.DEC,
105             TokenTypes.UNARY_MINUS,
106             TokenTypes.UNARY_PLUS,
107             TokenTypes.BNOT,
108             TokenTypes.LNOT,
109             TokenTypes.DOT,
110             TokenTypes.ARRAY_DECLARATOR,
111             TokenTypes.INDEX_OP,
112         };
113     }
114 
115     @Override
116     public int[] getAcceptableTokens() {
117         return new int[] {
118             TokenTypes.ARRAY_INIT,
119             TokenTypes.AT,
120             TokenTypes.INC,
121             TokenTypes.DEC,
122             TokenTypes.UNARY_MINUS,
123             TokenTypes.UNARY_PLUS,
124             TokenTypes.BNOT,
125             TokenTypes.LNOT,
126             TokenTypes.DOT,
127             TokenTypes.TYPECAST,
128             TokenTypes.ARRAY_DECLARATOR,
129             TokenTypes.INDEX_OP,
130             TokenTypes.LITERAL_SYNCHRONIZED,
131             TokenTypes.METHOD_REF,
132         };
133     }
134 
135     @Override
136     public int[] getRequiredTokens() {
137         return CommonUtils.EMPTY_INT_ARRAY;
138     }
139 
140     /**
141      * Control whether whitespace is flagged at linebreaks.
142      * @param allowLineBreaks whether whitespace should be
143      *     flagged at linebreaks.
144      */
145     public void setAllowLineBreaks(boolean allowLineBreaks) {
146         this.allowLineBreaks = allowLineBreaks;
147     }
148 
149     @Override
150     public void visitToken(DetailAST ast) {
151         final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast);
152 
153         if (whitespaceFollowedAst.getNextSibling() == null
154                 || whitespaceFollowedAst.getNextSibling().getType() != TokenTypes.ANNOTATIONS) {
155             final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst);
156             final int whitespaceLineNo = whitespaceFollowedAst.getLineNo();
157 
158             if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) {
159                 log(whitespaceLineNo, whitespaceColumnNo,
160                         MSG_KEY, whitespaceFollowedAst.getText());
161             }
162         }
163     }
164 
165     /**
166      * For a visited ast node returns node that should be checked
167      * for not being followed by whitespace.
168      * @param ast
169      *        , visited node.
170      * @return node before ast.
171      */
172     private static DetailAST getWhitespaceFollowedNode(DetailAST ast) {
173         final DetailAST whitespaceFollowedAst;
174         switch (ast.getType()) {
175             case TokenTypes.TYPECAST:
176                 whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN);
177                 break;
178             case TokenTypes.ARRAY_DECLARATOR:
179                 whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast);
180                 break;
181             case TokenTypes.INDEX_OP:
182                 whitespaceFollowedAst = getIndexOpPreviousElement(ast);
183                 break;
184             default:
185                 whitespaceFollowedAst = ast;
186         }
187         return whitespaceFollowedAst;
188     }
189 
190     /**
191      * Gets position after token (place of possible redundant whitespace).
192      * @param ast Node representing token.
193      * @return position after token.
194      */
195     private static int getPositionAfter(DetailAST ast) {
196         final int after;
197         //If target of possible redundant whitespace is in method definition.
198         if (ast.getType() == TokenTypes.IDENT
199                 && ast.getNextSibling() != null
200                 && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
201             final DetailAST methodDef = ast.getParent();
202             final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
203             after = endOfParams.getColumnNo() + 1;
204         }
205         else {
206             after = ast.getColumnNo() + ast.getText().length();
207         }
208         return after;
209     }
210 
211     /**
212      * Checks if there is unwanted whitespace after the visited node.
213      * @param ast
214      *        , visited node.
215      * @param whitespaceColumnNo
216      *        , column number of a possible whitespace.
217      * @param whitespaceLineNo
218      *        , line number of a possible whitespace.
219      * @return true if whitespace found.
220      */
221     private boolean hasTrailingWhitespace(DetailAST ast,
222         int whitespaceColumnNo, int whitespaceLineNo) {
223         final boolean result;
224         final int astLineNo = ast.getLineNo();
225         final String line = getLine(astLineNo - 1);
226         if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length()) {
227             result = Character.isWhitespace(line.charAt(whitespaceColumnNo));
228         }
229         else {
230             result = !allowLineBreaks;
231         }
232         return result;
233     }
234 
235     /**
236      * Returns proper argument for getPositionAfter method, it is a token after
237      * {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR}, in can be {@link TokenTypes#RBRACK
238      * RBRACK}, {@link TokenTypes#IDENT IDENT} or an array type definition (literal).
239      * @param ast
240      *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
241      * @return previous node by text order.
242      */
243     private static DetailAST getArrayDeclaratorPreviousElement(DetailAST ast) {
244         final DetailAST previousElement;
245         final DetailAST firstChild = ast.getFirstChild();
246         if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) {
247             // second or higher array index
248             previousElement = firstChild.findFirstToken(TokenTypes.RBRACK);
249         }
250         else {
251             // first array index, is preceded with identifier or type
252             final DetailAST parent = getFirstNonArrayDeclaratorParent(ast);
253             switch (parent.getType()) {
254                 // generics
255                 case TokenTypes.TYPE_ARGUMENT:
256                     final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE);
257                     if (wildcard == null) {
258                         // usual generic type argument like <char[]>
259                         previousElement = getTypeLastNode(ast);
260                     }
261                     else {
262                         // constructions with wildcard like <? extends String[]>
263                         previousElement = getTypeLastNode(ast.getFirstChild());
264                     }
265                     break;
266                 // 'new' is a special case with its own subtree structure
267                 case TokenTypes.LITERAL_NEW:
268                     previousElement = getTypeLastNode(parent);
269                     break;
270                 // mundane array declaration, can be either java style or C style
271                 case TokenTypes.TYPE:
272                     previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent);
273                     break;
274                 // i.e. boolean[].class
275                 case TokenTypes.DOT:
276                     previousElement = getTypeLastNode(ast);
277                     break;
278                 // java 8 method reference
279                 case TokenTypes.METHOD_REF:
280                     final DetailAST ident = getIdentLastToken(ast);
281                     if (ident == null) {
282                         //i.e. int[]::new
283                         previousElement = ast.getFirstChild();
284                     }
285                     else {
286                         previousElement = ident;
287                     }
288                     break;
289                 default:
290                     throw new IllegalStateException("unexpected ast syntax " + parent);
291             }
292         }
293         return previousElement;
294     }
295 
296     /**
297      * Gets previous node for {@link TokenTypes#INDEX_OP INDEX_OP} token
298      * for usage in getPositionAfter method, it is a simplified copy of
299      * getArrayDeclaratorPreviousElement method.
300      * @param ast
301      *        , {@link TokenTypes#INDEX_OP INDEX_OP} node.
302      * @return previous node by text order.
303      */
304     private static DetailAST getIndexOpPreviousElement(DetailAST ast) {
305         final DetailAST result;
306         final DetailAST firstChild = ast.getFirstChild();
307         if (firstChild.getType() == TokenTypes.INDEX_OP) {
308             // second or higher array index
309             result = firstChild.findFirstToken(TokenTypes.RBRACK);
310         }
311         else {
312             final DetailAST ident = getIdentLastToken(ast);
313             if (ident == null) {
314                 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
315                 // construction like new int[]{1}[0]
316                 if (rparen == null) {
317                     final DetailAST lastChild = firstChild.getLastChild();
318                     result = lastChild.findFirstToken(TokenTypes.RCURLY);
319                 }
320                 // construction like ((byte[]) pixels)[0]
321                 else {
322                     result = rparen;
323                 }
324             }
325             else {
326                 result = ident;
327             }
328         }
329         return result;
330     }
331 
332     /**
333      * Get node that owns {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} sequence.
334      * @param ast
335      *        , {@link TokenTypes#ARRAY_DECLARATOR ARRAY_DECLARATOR} node.
336      * @return owner node.
337      */
338     private static DetailAST getFirstNonArrayDeclaratorParent(DetailAST ast) {
339         DetailAST parent = ast.getParent();
340         while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) {
341             parent = parent.getParent();
342         }
343         return parent;
344     }
345 
346     /**
347      * Searches parameter node for a type node.
348      * Returns it or its last node if it has an extended structure.
349      * @param ast
350      *        , subject node.
351      * @return type node.
352      */
353     private static DetailAST getTypeLastNode(DetailAST ast) {
354         DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
355         if (result == null) {
356             result = getIdentLastToken(ast);
357             if (result == null) {
358                 //primitive literal expected
359                 result = ast.getFirstChild();
360             }
361         }
362         else {
363             result = result.findFirstToken(TokenTypes.GENERIC_END);
364         }
365         return result;
366     }
367 
368     /**
369      * Finds previous node by text order for an array declarator,
370      * which parent type is {@link TokenTypes#TYPE TYPE}.
371      * @param ast
372      *        , array declarator node.
373      * @param parent
374      *        , its parent node.
375      * @return previous node by text order.
376      */
377     private static DetailAST getPreviousNodeWithParentOfTypeAst(DetailAST ast, DetailAST parent) {
378         final DetailAST previousElement;
379         final DetailAST ident = getIdentLastToken(parent.getParent());
380         final DetailAST lastTypeNode = getTypeLastNode(ast);
381         // sometimes there are ident-less sentences
382         // i.e. "(Object[]) null", but in casual case should be
383         // checked whether ident or lastTypeNode has preceding position
384         // determining if it is java style or C style
385         if (ident == null || ident.getLineNo() > ast.getLineNo()) {
386             previousElement = lastTypeNode;
387         }
388         else if (ident.getLineNo() < ast.getLineNo()) {
389             previousElement = ident;
390         }
391         //ident and lastTypeNode lay on one line
392         else {
393             final int instanceOfSize = 13;
394             // +2 because ast has `[]` after the ident
395             if (ident.getColumnNo() >= ast.getColumnNo() + 2
396                 // +13 because ident (at most 1 character) is followed by
397                 // ' instanceof ' (12 characters)
398                 || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) {
399                 previousElement = lastTypeNode;
400             }
401             else {
402                 previousElement = ident;
403             }
404         }
405         return previousElement;
406     }
407 
408     /**
409      * Gets leftmost token of identifier.
410      * @param ast
411      *        , token possibly possessing an identifier.
412      * @return leftmost token of identifier.
413      */
414     private static DetailAST getIdentLastToken(DetailAST ast) {
415         // single identifier token as a name is the most common case
416         DetailAST result = ast.findFirstToken(TokenTypes.IDENT);
417         if (result == null) {
418             final DetailAST dot = ast.findFirstToken(TokenTypes.DOT);
419             // method call case
420             if (dot == null) {
421                 final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL);
422                 if (methodCall != null) {
423                     result = methodCall.findFirstToken(TokenTypes.RPAREN);
424                 }
425             }
426             // qualified name case
427             else {
428                 if (dot.findFirstToken(TokenTypes.DOT) == null) {
429                     result = dot.getFirstChild().getNextSibling();
430                 }
431                 else {
432                     result = dot.findFirstToken(TokenTypes.IDENT);
433                 }
434             }
435         }
436         return result;
437     }
438 
439 }