View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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   *
39   * @since 3.0
40   */
41  @StatelessCheck
42  public class NoWhitespaceBeforeCheck
43      extends AbstractCheck {
44  
45      /**
46       * A key is pointing to the warning message text in "messages.properties"
47       * file.
48       */
49      public static final String MSG_KEY = "ws.preceded";
50  
51      /** Control whether whitespace is allowed if the token is at a linebreak. */
52      private boolean allowLineBreaks;
53  
54      @Override
55      public int[] getDefaultTokens() {
56          return new int[] {
57              TokenTypes.COMMA,
58              TokenTypes.SEMI,
59              TokenTypes.POST_INC,
60              TokenTypes.POST_DEC,
61              TokenTypes.ELLIPSIS,
62              TokenTypes.LABELED_STAT,
63          };
64      }
65  
66      @Override
67      public int[] getAcceptableTokens() {
68          return new int[] {
69              TokenTypes.COMMA,
70              TokenTypes.SEMI,
71              TokenTypes.POST_INC,
72              TokenTypes.POST_DEC,
73              TokenTypes.DOT,
74              TokenTypes.GENERIC_START,
75              TokenTypes.GENERIC_END,
76              TokenTypes.ELLIPSIS,
77              TokenTypes.LABELED_STAT,
78              TokenTypes.METHOD_REF,
79          };
80      }
81  
82      @Override
83      public int[] getRequiredTokens() {
84          return CommonUtil.EMPTY_INT_ARRAY;
85      }
86  
87      @Override
88      public void visitToken(DetailAST ast) {
89          final int[] line = getLineCodePoints(ast.getLineNo() - 1);
90          final int columnNoBeforeToken = ast.getColumnNo() - 1;
91          final boolean isFirstToken = columnNoBeforeToken == -1;
92  
93          if ((isFirstToken || CommonUtil.isCodePointWhitespace(line, columnNoBeforeToken))
94                  && !isInEmptyForInitializerOrCondition(ast)) {
95              final boolean isViolation = !allowLineBreaks
96                      || !isFirstToken
97                      && !CodePointUtil.hasWhitespaceBefore(columnNoBeforeToken, line);
98  
99              if (isViolation) {
100                 log(ast, MSG_KEY, ast.getText());
101             }
102         }
103     }
104 
105     /**
106      * Checks that semicolon is in empty for initializer or condition.
107      *
108      * @param semicolonAst DetailAST of semicolon.
109      * @return true if semicolon is in empty for initializer or condition.
110      */
111     private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
112         boolean result = false;
113         final DetailAST sibling = semicolonAst.getPreviousSibling();
114         if (sibling != null
115                 && (sibling.getType() == TokenTypes.FOR_INIT
116                         || sibling.getType() == TokenTypes.FOR_CONDITION)
117                 && !sibling.hasChildren()) {
118             result = true;
119         }
120         return result;
121     }
122 
123     /**
124      * Setter to control whether whitespace is allowed if the token is at a linebreak.
125      *
126      * @param allowLineBreaks whether whitespace should be
127      *     flagged at line breaks.
128      * @since 3.0
129      */
130     public void setAllowLineBreaks(boolean allowLineBreaks) {
131         this.allowLineBreaks = allowLineBreaks;
132     }
133 
134 }