001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.whitespace;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
027
028/**
029 * <p>
030 * Checks that there is no whitespace before a token.
031 * More specifically, it checks that it is not preceded with whitespace,
032 * or (if linebreaks are allowed) all characters on the line before are
033 * whitespace. To allow linebreaks before a token, set property
034 * {@code allowLineBreaks} to {@code true}. No check occurs before semi-colons in empty
035 * for loop initializers or conditions.
036 * </p>
037 * <ul>
038 * <li>
039 * Property {@code allowLineBreaks} - Control whether whitespace is allowed
040 * if the token is at a linebreak.
041 * Type is {@code boolean}.
042 * Default value is {@code false}.
043 * </li>
044 * <li>
045 * Property {@code tokens} - tokens to check
046 * Type is {@code java.lang.String[]}.
047 * Validation type is {@code tokenSet}.
048 * Default value is:
049 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA">
050 * COMMA</a>,
051 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI">
052 * SEMI</a>,
053 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_INC">
054 * POST_INC</a>,
055 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_DEC">
056 * POST_DEC</a>,
057 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS">
058 * ELLIPSIS</a>,
059 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LABELED_STAT">
060 * LABELED_STAT</a>.
061 * </li>
062 * </ul>
063 * <p>
064 * To configure the check:
065 * </p>
066 * <pre>
067 * &lt;module name=&quot;NoWhitespaceBefore&quot;/&gt;
068 * </pre>
069 * <p>Example:</p>
070 * <pre>
071 * int foo;
072 * foo ++; // violation, whitespace before '++' is not allowed
073 * foo++; // OK
074 * for (int i = 0 ; i &lt; 5; i++) {}  // violation
075 *            // ^ whitespace before ';' is not allowed
076 * for (int i = 0; i &lt; 5; i++) {} // OK
077 * int[][] array = { { 1, 2 }
078 *                 , { 3, 4 } }; // violation, whitespace before ',' is not allowed
079 * int[][] array2 = { { 1, 2 },
080 *                    { 3, 4 } }; // OK
081 * Lists.charactersOf("foo").listIterator()
082 *        .forEachRemaining(System.out::print)
083 *        ; // violation, whitespace before ';' is not allowed
084 *   {
085 *     label1 : // violation, whitespace before ':' is not allowed
086 *     for (int i = 0; i &lt; 10; i++) {}
087 *   }
088 *
089 *   {
090 *     label2: // OK
091 *     while (true) {}
092 *   }
093 * </pre>
094 * <p>To configure the check to allow linebreaks before default tokens:</p>
095 * <pre>
096 * &lt;module name=&quot;NoWhitespaceBefore&quot;&gt;
097 *   &lt;property name=&quot;allowLineBreaks&quot; value=&quot;true&quot;/&gt;
098 * &lt;/module&gt;
099 * </pre>
100 * <p>Example:</p>
101 * <pre>
102 * int[][] array = { { 1, 2 }
103 *                 , { 3, 4 } }; // OK, linebreak is allowed before ','
104 * int[][] array2 = { { 1, 2 },
105 *                    { 3, 4 } }; // OK, ideal code
106 * void ellipsisExample(String ...params) {}; // violation, whitespace before '...' is not allowed
107 * void ellipsisExample2(String
108 *                         ...params) {}; //OK, linebreak is allowed before '...'
109 * Lists.charactersOf("foo")
110 *        .listIterator()
111 *        .forEachRemaining(System.out::print); // OK
112 * </pre>
113 * <p>
114 *     To Configure the check to restrict the use of whitespace before METHOD_REF and DOT tokens:
115 * </p>
116 * <pre>
117 * &lt;module name=&quot;NoWhitespaceBefore&quot;&gt;
118 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_REF&quot;/&gt;
119 *   &lt;property name=&quot;tokens&quot; value=&quot;DOT&quot;/&gt;
120 * &lt;/module&gt;
121 * </pre>
122 * <p>Example:</p>
123 * <pre>
124 * Lists.charactersOf("foo").listIterator()
125 *        .forEachRemaining(System.out::print); // violation, whitespace before '.' is not allowed
126 * Lists.charactersOf("foo").listIterator().forEachRemaining(System.out ::print); // violation,
127 *                           // whitespace before '::' is not allowed  ^
128 * Lists.charactersOf("foo").listIterator().forEachRemaining(System.out::print); // OK
129 * </pre>
130 * <p>
131 *     To configure the check to allow linebreak before METHOD_REF and DOT tokens:
132 * </p>
133 * <pre>
134 * &lt;module name=&quot;NoWhitespaceBefore&quot;&gt;
135 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_REF&quot;/&gt;
136 *   &lt;property name=&quot;tokens&quot; value=&quot;DOT&quot;/&gt;
137 *   &lt;property name=&quot;allowLineBreaks&quot; value=&quot;true&quot;/&gt;
138 * &lt;/module&gt;
139 * </pre>
140 * <p>Example:</p>
141 * <pre>
142 * Lists .charactersOf("foo") //violation, whitespace before '.' is not allowed
143 *         .listIterator()
144 *         .forEachRemaining(System.out ::print); // violation,
145 *                                  // ^ whitespace before '::' is not allowed
146 * Lists.charactersOf("foo")
147 *        .listIterator()
148 *        .forEachRemaining(System.out::print); // OK
149 * </pre>
150 * <p>
151 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
152 * </p>
153 * <p>
154 * Violation Message Keys:
155 * </p>
156 * <ul>
157 * <li>
158 * {@code ws.preceded}
159 * </li>
160 * </ul>
161 *
162 * @since 3.0
163 */
164@StatelessCheck
165public class NoWhitespaceBeforeCheck
166    extends AbstractCheck {
167
168    /**
169     * A key is pointing to the warning message text in "messages.properties"
170     * file.
171     */
172    public static final String MSG_KEY = "ws.preceded";
173
174    /** Control whether whitespace is allowed if the token is at a linebreak. */
175    private boolean allowLineBreaks;
176
177    @Override
178    public int[] getDefaultTokens() {
179        return new int[] {
180            TokenTypes.COMMA,
181            TokenTypes.SEMI,
182            TokenTypes.POST_INC,
183            TokenTypes.POST_DEC,
184            TokenTypes.ELLIPSIS,
185            TokenTypes.LABELED_STAT,
186        };
187    }
188
189    @Override
190    public int[] getAcceptableTokens() {
191        return new int[] {
192            TokenTypes.COMMA,
193            TokenTypes.SEMI,
194            TokenTypes.POST_INC,
195            TokenTypes.POST_DEC,
196            TokenTypes.DOT,
197            TokenTypes.GENERIC_START,
198            TokenTypes.GENERIC_END,
199            TokenTypes.ELLIPSIS,
200            TokenTypes.LABELED_STAT,
201            TokenTypes.METHOD_REF,
202        };
203    }
204
205    @Override
206    public int[] getRequiredTokens() {
207        return CommonUtil.EMPTY_INT_ARRAY;
208    }
209
210    @Override
211    public void visitToken(DetailAST ast) {
212        final String line = getLine(ast.getLineNo() - 1);
213        final int before = ast.getColumnNo() - 1;
214        final int[] codePoints = line.codePoints().toArray();
215
216        if ((before == -1 || CommonUtil.isCodePointWhitespace(codePoints, before))
217                && !isInEmptyForInitializerOrCondition(ast)) {
218            boolean flag = !allowLineBreaks;
219            // verify all characters before '.' are whitespace
220            for (int i = 0; i <= before - 1; i++) {
221                if (!CommonUtil.isCodePointWhitespace(codePoints, i)) {
222                    flag = true;
223                    break;
224                }
225            }
226            if (flag) {
227                log(ast, MSG_KEY, ast.getText());
228            }
229        }
230    }
231
232    /**
233     * Checks that semicolon is in empty for initializer or condition.
234     *
235     * @param semicolonAst DetailAST of semicolon.
236     * @return true if semicolon is in empty for initializer or condition.
237     */
238    private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
239        boolean result = false;
240        final DetailAST sibling = semicolonAst.getPreviousSibling();
241        if (sibling != null
242                && (sibling.getType() == TokenTypes.FOR_INIT
243                        || sibling.getType() == TokenTypes.FOR_CONDITION)
244                && !sibling.hasChildren()) {
245            result = true;
246        }
247        return result;
248    }
249
250    /**
251     * Setter to control whether whitespace is allowed if the token is at a linebreak.
252     *
253     * @param allowLineBreaks whether whitespace should be
254     *     flagged at line breaks.
255     */
256    public void setAllowLineBreaks(boolean allowLineBreaks) {
257        this.allowLineBreaks = allowLineBreaks;
258    }
259
260}