View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.CodePointUtil;
27  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
28  
29  /**
30   * <div>
31   * Checks that there is no whitespace before a token.
32   * More specifically, it checks that it is not preceded with whitespace,
33   * or (if linebreaks are allowed) all characters on the line before are
34   * whitespace. To allow linebreaks before a token, set property
35   * {@code allowLineBreaks} to {@code true}. No check occurs before semicolons in empty
36   * for loop initializers or conditions.
37   * </div>
38   * <ul>
39   * <li>
40   * Property {@code allowLineBreaks} - Control whether whitespace is allowed
41   * if the token is at a linebreak.
42   * Type is {@code boolean}.
43   * Default value is {@code false}.
44   * </li>
45   * <li>
46   * Property {@code tokens} - tokens to check
47   * Type is {@code java.lang.String[]}.
48   * Validation type is {@code tokenSet}.
49   * Default value is:
50   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA">
51   * COMMA</a>,
52   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI">
53   * SEMI</a>,
54   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_INC">
55   * POST_INC</a>,
56   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_DEC">
57   * POST_DEC</a>,
58   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS">
59   * ELLIPSIS</a>,
60   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LABELED_STAT">
61   * LABELED_STAT</a>.
62   * </li>
63   * </ul>
64   *
65   * <p>
66   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
67   * </p>
68   *
69   * <p>
70   * Violation Message Keys:
71   * </p>
72   * <ul>
73   * <li>
74   * {@code ws.preceded}
75   * </li>
76   * </ul>
77   *
78   * @since 3.0
79   */
80  @StatelessCheck
81  public class NoWhitespaceBeforeCheck
82      extends AbstractCheck {
83  
84      /**
85       * A key is pointing to the warning message text in "messages.properties"
86       * file.
87       */
88      public static final String MSG_KEY = "ws.preceded";
89  
90      /** Control whether whitespace is allowed if the token is at a linebreak. */
91      private boolean allowLineBreaks;
92  
93      @Override
94      public int[] getDefaultTokens() {
95          return new int[] {
96              TokenTypes.COMMA,
97              TokenTypes.SEMI,
98              TokenTypes.POST_INC,
99              TokenTypes.POST_DEC,
100             TokenTypes.ELLIPSIS,
101             TokenTypes.LABELED_STAT,
102         };
103     }
104 
105     @Override
106     public int[] getAcceptableTokens() {
107         return new int[] {
108             TokenTypes.COMMA,
109             TokenTypes.SEMI,
110             TokenTypes.POST_INC,
111             TokenTypes.POST_DEC,
112             TokenTypes.DOT,
113             TokenTypes.GENERIC_START,
114             TokenTypes.GENERIC_END,
115             TokenTypes.ELLIPSIS,
116             TokenTypes.LABELED_STAT,
117             TokenTypes.METHOD_REF,
118         };
119     }
120 
121     @Override
122     public int[] getRequiredTokens() {
123         return CommonUtil.EMPTY_INT_ARRAY;
124     }
125 
126     @Override
127     public void visitToken(DetailAST ast) {
128         final int[] line = getLineCodePoints(ast.getLineNo() - 1);
129         final int columnNoBeforeToken = ast.getColumnNo() - 1;
130         final boolean isFirstToken = columnNoBeforeToken == -1;
131 
132         if ((isFirstToken || CommonUtil.isCodePointWhitespace(line, columnNoBeforeToken))
133                 && !isInEmptyForInitializerOrCondition(ast)) {
134             final boolean isViolation = !allowLineBreaks
135                     || !isFirstToken
136                     && !CodePointUtil.hasWhitespaceBefore(columnNoBeforeToken, line);
137 
138             if (isViolation) {
139                 log(ast, MSG_KEY, ast.getText());
140             }
141         }
142     }
143 
144     /**
145      * Checks that semicolon is in empty for initializer or condition.
146      *
147      * @param semicolonAst DetailAST of semicolon.
148      * @return true if semicolon is in empty for initializer or condition.
149      */
150     private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
151         boolean result = false;
152         final DetailAST sibling = semicolonAst.getPreviousSibling();
153         if (sibling != null
154                 && (sibling.getType() == TokenTypes.FOR_INIT
155                         || sibling.getType() == TokenTypes.FOR_CONDITION)
156                 && !sibling.hasChildren()) {
157             result = true;
158         }
159         return result;
160     }
161 
162     /**
163      * Setter to control whether whitespace is allowed if the token is at a linebreak.
164      *
165      * @param allowLineBreaks whether whitespace should be
166      *     flagged at line breaks.
167      * @since 3.0
168      */
169     public void setAllowLineBreaks(boolean allowLineBreaks) {
170         this.allowLineBreaks = allowLineBreaks;
171     }
172 
173 }