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 }