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