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 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   */
63  @StatelessCheck
64  public class NoWhitespaceBeforeCheck
65      extends AbstractCheck {
66  
67      /**
68       * A key is pointing to the warning message text in "messages.properties"
69       * file.
70       */
71      public static final String MSG_KEY = "ws.preceded";
72  
73      /** Whether whitespace is allowed if the AST is at a linebreak. */
74      private boolean allowLineBreaks;
75  
76      @Override
77      public int[] getDefaultTokens() {
78          return new int[] {
79              TokenTypes.COMMA,
80              TokenTypes.SEMI,
81              TokenTypes.POST_INC,
82              TokenTypes.POST_DEC,
83              TokenTypes.ELLIPSIS,
84          };
85      }
86  
87      @Override
88      public int[] getAcceptableTokens() {
89          return new int[] {
90              TokenTypes.COMMA,
91              TokenTypes.SEMI,
92              TokenTypes.POST_INC,
93              TokenTypes.POST_DEC,
94              TokenTypes.DOT,
95              TokenTypes.GENERIC_START,
96              TokenTypes.GENERIC_END,
97              TokenTypes.ELLIPSIS,
98              TokenTypes.METHOD_REF,
99          };
100     }
101 
102     @Override
103     public int[] getRequiredTokens() {
104         return CommonUtil.EMPTY_INT_ARRAY;
105     }
106 
107     @Override
108     public void visitToken(DetailAST ast) {
109         final String line = getLine(ast.getLineNo() - 1);
110         final int before = ast.getColumnNo() - 1;
111 
112         if ((before == -1 || Character.isWhitespace(line.charAt(before)))
113                 && !isInEmptyForInitializerOrCondition(ast)) {
114             boolean flag = !allowLineBreaks;
115             // verify all characters before '.' are whitespace
116             for (int i = 0; i <= before - 1; i++) {
117                 if (!Character.isWhitespace(line.charAt(i))) {
118                     flag = true;
119                     break;
120                 }
121             }
122             if (flag) {
123                 log(ast, MSG_KEY, ast.getText());
124             }
125         }
126     }
127 
128     /**
129      * Checks that semicolon is in empty for initializer or condition.
130      * @param semicolonAst DetailAST of semicolon.
131      * @return true if semicolon is in empty for initializer or condition.
132      */
133     private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
134         boolean result = false;
135         final DetailAST sibling = semicolonAst.getPreviousSibling();
136         if (sibling != null
137                 && (sibling.getType() == TokenTypes.FOR_INIT
138                         || sibling.getType() == TokenTypes.FOR_CONDITION)
139                 && sibling.getChildCount() == 0) {
140             result = true;
141         }
142         return result;
143     }
144 
145     /**
146      * Control whether whitespace is flagged at line breaks.
147      * @param allowLineBreaks whether whitespace should be
148      *     flagged at line breaks.
149      */
150     public void setAllowLineBreaks(boolean allowLineBreaks) {
151         this.allowLineBreaks = allowLineBreaks;
152     }
153 
154 }