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 }