Coverage Report - com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
NoWhitespaceAfterCheck
100%
112/112
100%
64/64
3.5
 
 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.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  19
 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  19
     private boolean allowLineBreaks = true;
 97  
 
 98  
     @Override
 99  
     public int[] getDefaultTokens() {
 100  9
         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  13
         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  27
         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  2
         this.allowLineBreaks = allowLineBreaks;
 147  2
     }
 148  
 
 149  
     @Override
 150  
     public void visitToken(DetailAST ast) {
 151  309
         final DetailAST whitespaceFollowedAst = getWhitespaceFollowedNode(ast);
 152  
 
 153  308
         if (whitespaceFollowedAst.getNextSibling() == null
 154  178
                 || whitespaceFollowedAst.getNextSibling().getType() != TokenTypes.ANNOTATIONS) {
 155  304
             final int whitespaceColumnNo = getPositionAfter(whitespaceFollowedAst);
 156  304
             final int whitespaceLineNo = whitespaceFollowedAst.getLineNo();
 157  
 
 158  304
             if (hasTrailingWhitespace(ast, whitespaceColumnNo, whitespaceLineNo)) {
 159  180
                 log(whitespaceLineNo, whitespaceColumnNo,
 160  90
                         MSG_KEY, whitespaceFollowedAst.getText());
 161  
             }
 162  
         }
 163  308
     }
 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  309
         switch (ast.getType()) {
 175  
             case TokenTypes.TYPECAST:
 176  5
                 whitespaceFollowedAst = ast.findFirstToken(TokenTypes.RPAREN);
 177  5
                 break;
 178  
             case TokenTypes.ARRAY_DECLARATOR:
 179  175
                 whitespaceFollowedAst = getArrayDeclaratorPreviousElement(ast);
 180  174
                 break;
 181  
             case TokenTypes.INDEX_OP:
 182  46
                 whitespaceFollowedAst = getIndexOpPreviousElement(ast);
 183  46
                 break;
 184  
             default:
 185  83
                 whitespaceFollowedAst = ast;
 186  
         }
 187  308
         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  304
         if (ast.getType() == TokenTypes.IDENT
 199  63
                 && ast.getNextSibling() != null
 200  56
                 && ast.getNextSibling().getType() == TokenTypes.LPAREN) {
 201  6
             final DetailAST methodDef = ast.getParent();
 202  6
             final DetailAST endOfParams = methodDef.findFirstToken(TokenTypes.RPAREN);
 203  6
             after = endOfParams.getColumnNo() + 1;
 204  6
         }
 205  
         else {
 206  298
             after = ast.getColumnNo() + ast.getText().length();
 207  
         }
 208  304
         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  304
         final int astLineNo = ast.getLineNo();
 225  304
         final String line = getLine(astLineNo - 1);
 226  304
         if (astLineNo == whitespaceLineNo && whitespaceColumnNo < line.length()) {
 227  294
             result = Character.isWhitespace(line.charAt(whitespaceColumnNo));
 228  
         }
 229  
         else {
 230  10
             result = !allowLineBreaks;
 231  
         }
 232  304
         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  175
         final DetailAST firstChild = ast.getFirstChild();
 246  175
         if (firstChild.getType() == TokenTypes.ARRAY_DECLARATOR) {
 247  
             // second or higher array index
 248  70
             previousElement = firstChild.findFirstToken(TokenTypes.RBRACK);
 249  
         }
 250  
         else {
 251  
             // first array index, is preceded with identifier or type
 252  105
             final DetailAST parent = getFirstNonArrayDeclaratorParent(ast);
 253  105
             switch (parent.getType()) {
 254  
                 // generics
 255  
                 case TokenTypes.TYPE_ARGUMENT:
 256  9
                     final DetailAST wildcard = parent.findFirstToken(TokenTypes.WILDCARD_TYPE);
 257  9
                     if (wildcard == null) {
 258  
                         // usual generic type argument like <char[]>
 259  5
                         previousElement = getTypeLastNode(ast);
 260  
                     }
 261  
                     else {
 262  
                         // constructions with wildcard like <? extends String[]>
 263  4
                         previousElement = getTypeLastNode(ast.getFirstChild());
 264  
                     }
 265  4
                     break;
 266  
                 // 'new' is a special case with its own subtree structure
 267  
                 case TokenTypes.LITERAL_NEW:
 268  28
                     previousElement = getTypeLastNode(parent);
 269  28
                     break;
 270  
                 // mundane array declaration, can be either java style or C style
 271  
                 case TokenTypes.TYPE:
 272  63
                     previousElement = getPreviousNodeWithParentOfTypeAst(ast, parent);
 273  63
                     break;
 274  
                 // i.e. boolean[].class
 275  
                 case TokenTypes.DOT:
 276  2
                     previousElement = getTypeLastNode(ast);
 277  2
                     break;
 278  
                 // java 8 method reference
 279  
                 case TokenTypes.METHOD_REF:
 280  2
                     final DetailAST ident = getIdentLastToken(ast);
 281  2
                     if (ident == null) {
 282  
                         //i.e. int[]::new
 283  1
                         previousElement = ast.getFirstChild();
 284  
                     }
 285  
                     else {
 286  1
                         previousElement = ident;
 287  
                     }
 288  1
                     break;
 289  
                 default:
 290  1
                     throw new IllegalStateException("unexpected ast syntax " + parent);
 291  
             }
 292  
         }
 293  174
         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  46
         final DetailAST firstChild = ast.getFirstChild();
 307  46
         if (firstChild.getType() == TokenTypes.INDEX_OP) {
 308  
             // second or higher array index
 309  25
             result = firstChild.findFirstToken(TokenTypes.RBRACK);
 310  
         }
 311  
         else {
 312  21
             final DetailAST ident = getIdentLastToken(ast);
 313  21
             if (ident == null) {
 314  12
                 final DetailAST rparen = ast.findFirstToken(TokenTypes.RPAREN);
 315  
                 // construction like new int[]{1}[0]
 316  12
                 if (rparen == null) {
 317  11
                     final DetailAST lastChild = firstChild.getLastChild();
 318  11
                     result = lastChild.findFirstToken(TokenTypes.RCURLY);
 319  11
                 }
 320  
                 // construction like ((byte[]) pixels)[0]
 321  
                 else {
 322  1
                     result = rparen;
 323  
                 }
 324  12
             }
 325  
             else {
 326  9
                 result = ident;
 327  
             }
 328  
         }
 329  46
         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  105
         DetailAST parent = ast.getParent();
 340  175
         while (parent.getType() == TokenTypes.ARRAY_DECLARATOR) {
 341  70
             parent = parent.getParent();
 342  
         }
 343  105
         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  102
         DetailAST result = ast.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
 355  102
         if (result == null) {
 356  100
             result = getIdentLastToken(ast);
 357  100
             if (result == null) {
 358  
                 //primitive literal expected
 359  59
                 result = ast.getFirstChild();
 360  
             }
 361  
         }
 362  
         else {
 363  2
             result = result.findFirstToken(TokenTypes.GENERIC_END);
 364  
         }
 365  102
         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  63
         final DetailAST ident = getIdentLastToken(parent.getParent());
 380  63
         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  63
         if (ident == null || ident.getLineNo() > ast.getLineNo()) {
 386  3
             previousElement = lastTypeNode;
 387  
         }
 388  60
         else if (ident.getLineNo() < ast.getLineNo()) {
 389  1
             previousElement = ident;
 390  
         }
 391  
         //ident and lastTypeNode lay on one line
 392  
         else {
 393  59
             final int instanceOfSize = 13;
 394  
             // +2 because ast has `[]` after the ident
 395  59
             if (ident.getColumnNo() >= ast.getColumnNo() + 2
 396  
                 // +13 because ident (at most 1 character) is followed by
 397  
                 // ' instanceof ' (12 characters)
 398  24
                 || lastTypeNode.getColumnNo() >= ident.getColumnNo() + instanceOfSize) {
 399  40
                 previousElement = lastTypeNode;
 400  
             }
 401  
             else {
 402  19
                 previousElement = ident;
 403  
             }
 404  
         }
 405  63
         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  186
         DetailAST result = ast.findFirstToken(TokenTypes.IDENT);
 417  186
         if (result == null) {
 418  79
             final DetailAST dot = ast.findFirstToken(TokenTypes.DOT);
 419  
             // method call case
 420  79
             if (dot == null) {
 421  77
                 final DetailAST methodCall = ast.findFirstToken(TokenTypes.METHOD_CALL);
 422  77
                 if (methodCall != null) {
 423  3
                     result = methodCall.findFirstToken(TokenTypes.RPAREN);
 424  
                 }
 425  77
             }
 426  
             // qualified name case
 427  
             else {
 428  2
                 if (dot.findFirstToken(TokenTypes.DOT) == null) {
 429  1
                     result = dot.getFirstChild().getNextSibling();
 430  
                 }
 431  
                 else {
 432  1
                     result = dot.findFirstToken(TokenTypes.IDENT);
 433  
                 }
 434  
             }
 435  
         }
 436  186
         return result;
 437  
     }
 438  
 
 439  
 }