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 * <p>
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 * </p>
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 * <p>
65 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
66 * </p>
67 * <p>
68 * Violation Message Keys:
69 * </p>
70 * <ul>
71 * <li>
72 * {@code ws.preceded}
73 * </li>
74 * </ul>
75 *
76 * @since 3.0
77 */
78 @StatelessCheck
79 public class NoWhitespaceBeforeCheck
80 extends AbstractCheck {
81
82 /**
83 * A key is pointing to the warning message text in "messages.properties"
84 * file.
85 */
86 public static final String MSG_KEY = "ws.preceded";
87
88 /** Control whether whitespace is allowed if the token is at a linebreak. */
89 private boolean allowLineBreaks;
90
91 @Override
92 public int[] getDefaultTokens() {
93 return new int[] {
94 TokenTypes.COMMA,
95 TokenTypes.SEMI,
96 TokenTypes.POST_INC,
97 TokenTypes.POST_DEC,
98 TokenTypes.ELLIPSIS,
99 TokenTypes.LABELED_STAT,
100 };
101 }
102
103 @Override
104 public int[] getAcceptableTokens() {
105 return new int[] {
106 TokenTypes.COMMA,
107 TokenTypes.SEMI,
108 TokenTypes.POST_INC,
109 TokenTypes.POST_DEC,
110 TokenTypes.DOT,
111 TokenTypes.GENERIC_START,
112 TokenTypes.GENERIC_END,
113 TokenTypes.ELLIPSIS,
114 TokenTypes.LABELED_STAT,
115 TokenTypes.METHOD_REF,
116 };
117 }
118
119 @Override
120 public int[] getRequiredTokens() {
121 return CommonUtil.EMPTY_INT_ARRAY;
122 }
123
124 @Override
125 public void visitToken(DetailAST ast) {
126 final int[] line = getLineCodePoints(ast.getLineNo() - 1);
127 final int columnNoBeforeToken = ast.getColumnNo() - 1;
128 final boolean isFirstToken = columnNoBeforeToken == -1;
129
130 if ((isFirstToken || CommonUtil.isCodePointWhitespace(line, columnNoBeforeToken))
131 && !isInEmptyForInitializerOrCondition(ast)) {
132 final boolean isViolation = !allowLineBreaks
133 || !isFirstToken
134 && !CodePointUtil.hasWhitespaceBefore(columnNoBeforeToken, line);
135
136 if (isViolation) {
137 log(ast, MSG_KEY, ast.getText());
138 }
139 }
140 }
141
142 /**
143 * Checks that semicolon is in empty for initializer or condition.
144 *
145 * @param semicolonAst DetailAST of semicolon.
146 * @return true if semicolon is in empty for initializer or condition.
147 */
148 private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
149 boolean result = false;
150 final DetailAST sibling = semicolonAst.getPreviousSibling();
151 if (sibling != null
152 && (sibling.getType() == TokenTypes.FOR_INIT
153 || sibling.getType() == TokenTypes.FOR_CONDITION)
154 && !sibling.hasChildren()) {
155 result = true;
156 }
157 return result;
158 }
159
160 /**
161 * Setter to control whether whitespace is allowed if the token is at a linebreak.
162 *
163 * @param allowLineBreaks whether whitespace should be
164 * flagged at line breaks.
165 * @since 3.0
166 */
167 public void setAllowLineBreaks(boolean allowLineBreaks) {
168 this.allowLineBreaks = allowLineBreaks;
169 }
170
171 }