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.api.TokenTypes;
26  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
27  
28  /**
29   * <p>
30   * Checks that there is no whitespace before a token.
31   * More specifically, it checks that it is not preceded with whitespace,
32   * or (if line breaks are allowed) all characters on the line before are
33   * whitespace. To allow line breaks before a token, set property
34   * allowLineBreaks to true. No check occurs before semi-colons in empty
35   * for loop initializers or conditions.
36   * </p>
37   * <p> By default the check will check the following operators:
38   *  {@link TokenTypes#COMMA COMMA},
39   *  {@link TokenTypes#SEMI SEMI},
40   *  {@link TokenTypes#POST_DEC POST_DEC},
41   *  {@link TokenTypes#POST_INC POST_INC},
42   *  {@link TokenTypes#ELLIPSIS ELLIPSIS}.
43   * {@link TokenTypes#DOT DOT} is also an acceptable token in a configuration
44   * of this check.
45   * </p>
46   *
47   * <p>
48   * An example of how to configure the check is:
49   * </p>
50   * <pre>
51   * &lt;module name="NoWhitespaceBefore"/&gt;
52   * </pre>
53   * <p> An example of how to configure the check to allow line breaks before
54   * a {@link TokenTypes#DOT DOT} token is:
55   * </p>
56   * <pre>
57   * &lt;module name="NoWhitespaceBefore"&gt;
58   *     &lt;property name="tokens" value="DOT"/&gt;
59   *     &lt;property name="allowLineBreaks" value="true"/&gt;
60   * &lt;/module&gt;
61   * </pre>
62   * @author Rick Giles
63   * @author lkuehne
64   */
65  @StatelessCheck
66  public class NoWhitespaceBeforeCheck
67      extends AbstractCheck {
68  
69      /**
70       * A key is pointing to the warning message text in "messages.properties"
71       * file.
72       */
73      public static final String MSG_KEY = "ws.preceded";
74  
75      /** Whether whitespace is allowed if the AST is at a linebreak. */
76      private boolean allowLineBreaks;
77  
78      @Override
79      public int[] getDefaultTokens() {
80          return new int[] {
81              TokenTypes.COMMA,
82              TokenTypes.SEMI,
83              TokenTypes.POST_INC,
84              TokenTypes.POST_DEC,
85              TokenTypes.ELLIPSIS,
86          };
87      }
88  
89      @Override
90      public int[] getAcceptableTokens() {
91          return new int[] {
92              TokenTypes.COMMA,
93              TokenTypes.SEMI,
94              TokenTypes.POST_INC,
95              TokenTypes.POST_DEC,
96              TokenTypes.DOT,
97              TokenTypes.GENERIC_START,
98              TokenTypes.GENERIC_END,
99              TokenTypes.ELLIPSIS,
100             TokenTypes.METHOD_REF,
101         };
102     }
103 
104     @Override
105     public int[] getRequiredTokens() {
106         return CommonUtils.EMPTY_INT_ARRAY;
107     }
108 
109     @Override
110     public void visitToken(DetailAST ast) {
111         final String line = getLine(ast.getLineNo() - 1);
112         final int before = ast.getColumnNo() - 1;
113 
114         if ((before == -1 || Character.isWhitespace(line.charAt(before)))
115                 && !isInEmptyForInitializerOrCondition(ast)) {
116 
117             boolean flag = !allowLineBreaks;
118             // verify all characters before '.' are whitespace
119             for (int i = 0; !flag && i <= before - 1; i++) {
120                 if (!Character.isWhitespace(line.charAt(i))) {
121                     flag = true;
122                     break;
123                 }
124             }
125             if (flag) {
126                 log(ast.getLineNo(), before, MSG_KEY, ast.getText());
127             }
128         }
129     }
130 
131     /**
132      * Checks that semicolon is in empty for initializer or condition.
133      * @param semicolonAst DetailAST of semicolon.
134      * @return true if semicolon is in empty for initializer or condition.
135      */
136     private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
137         boolean result = false;
138         if (semicolonAst.getType() == TokenTypes.SEMI) {
139             final DetailAST sibling = semicolonAst.getPreviousSibling();
140             if (sibling != null
141                     && (sibling.getType() == TokenTypes.FOR_INIT
142                             || sibling.getType() == TokenTypes.FOR_CONDITION)
143                     && sibling.getChildCount() == 0) {
144                 result = true;
145             }
146         }
147         return result;
148     }
149 
150     /**
151      * Control whether whitespace is flagged at line breaks.
152      * @param allowLineBreaks whether whitespace should be
153      *     flagged at line breaks.
154      */
155     public void setAllowLineBreaks(boolean allowLineBreaks) {
156         this.allowLineBreaks = allowLineBreaks;
157     }
158 }