001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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 * Default value is {@code false}.
042 * </li>
043 * <li>
044 * Property {@code tokens} - tokens to check
045 * Default value is:
046 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMMA">
047 * COMMA</a>,
048 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SEMI">
049 * SEMI</a>,
050 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_INC">
051 * POST_INC</a>,
052 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#POST_DEC">
053 * POST_DEC</a>,
054 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ELLIPSIS">
055 * ELLIPSIS</a>,
056 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LABELED_STAT">
057 * LABELED_STAT</a>.
058 * </li>
059 * </ul>
060 * <p>
061 * To configure the check:
062 * </p>
063 * <pre>
064 * &lt;module name=&quot;NoWhitespaceBefore&quot;/&gt;
065 * </pre>
066 * <p>Example:</p>
067 * <pre>
068 * int foo;
069 * foo ++; // violation, whitespace before '++' is not allowed
070 * foo++; // OK
071 * for (int i = 0 ; i &lt; 5; i++) {}  // violation
072 *            // ^ whitespace before ';' is not allowed
073 * for (int i = 0; i &lt; 5; i++) {} // OK
074 * int[][] array = { { 1, 2 }
075 *                 , { 3, 4 } }; // violation, whitespace before ',' is not allowed
076 * int[][] array2 = { { 1, 2 },
077 *                    { 3, 4 } }; // OK
078 * Lists.charactersOf("foo").listIterator()
079 *        .forEachRemaining(System.out::print)
080 *        ; // violation, whitespace before ';' is not allowed
081 *   {
082 *     label1 : // violation, whitespace before ':' is not allowed
083 *     for (int i = 0; i &lt; 10; i++) {}
084 *   }
085 *
086 *   {
087 *     label2: // OK
088 *     while (true) {}
089 *   }
090 * </pre>
091 * <p>To configure the check to allow linebreaks before default tokens:</p>
092 * <pre>
093 * &lt;module name=&quot;NoWhitespaceBefore&quot;&gt;
094 *   &lt;property name=&quot;allowLineBreaks&quot; value=&quot;true&quot;/&gt;
095 * &lt;/module&gt;
096 * </pre>
097 * <p>Example:</p>
098 * <pre>
099 * int[][] array = { { 1, 2 }
100 *                 , { 3, 4 } }; // OK, linebreak is allowed before ','
101 * int[][] array2 = { { 1, 2 },
102 *                    { 3, 4 } }; // OK, ideal code
103 * void ellipsisExample(String ...params) {}; // violation, whitespace before '...' is not allowed
104 * void ellipsisExample2(String
105 *                         ...params) {}; //OK, linebreak is allowed before '...'
106 * Lists.charactersOf("foo")
107 *        .listIterator()
108 *        .forEachRemaining(System.out::print); // OK
109 * </pre>
110 * <p>
111 *     To Configure the check to restrict the use of whitespace before METHOD_REF and DOT tokens:
112 * </p>
113 * <pre>
114 * &lt;module name=&quot;NoWhitespaceBefore&quot;&gt;
115 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_REF&quot;/&gt;
116 *   &lt;property name=&quot;tokens&quot; value=&quot;DOT&quot;/&gt;
117 * &lt;/module&gt;
118 * </pre>
119 * <p>Example:</p>
120 * <pre>
121 * Lists.charactersOf("foo").listIterator()
122 *        .forEachRemaining(System.out::print); // violation, whitespace before '.' is not allowed
123 * Lists.charactersOf("foo").listIterator().forEachRemaining(System.out ::print); // violation,
124 *                           // whitespace before '::' is not allowed  ^
125 * Lists.charactersOf("foo").listIterator().forEachRemaining(System.out::print); // OK
126 * </pre>
127 * <p>
128 *     To configure the check to allow linebreak before METHOD_REF and DOT tokens:
129 * </p>
130 * <pre>
131 * &lt;module name=&quot;NoWhitespaceBefore&quot;&gt;
132 *   &lt;property name=&quot;tokens&quot; value=&quot;METHOD_REF&quot;/&gt;
133 *   &lt;property name=&quot;tokens&quot; value=&quot;DOT&quot;/&gt;
134 *   &lt;property name=&quot;allowLineBreaks&quot; value=&quot;true&quot;/&gt;
135 * &lt;/module&gt;
136 * </pre>
137 * <p>Example:</p>
138 * <pre>
139 * Lists .charactersOf("foo") //violation, whitespace before '.' is not allowed
140 *         .listIterator()
141 *         .forEachRemaining(System.out ::print); // violation,
142 *                                  // ^ whitespace before '::' is not allowed
143 * Lists.charactersOf("foo")
144 *        .listIterator()
145 *        .forEachRemaining(System.out::print); // OK
146 * </pre>
147 *
148 * @since 3.0
149 */
150@StatelessCheck
151public class NoWhitespaceBeforeCheck
152    extends AbstractCheck {
153
154    /**
155     * A key is pointing to the warning message text in "messages.properties"
156     * file.
157     */
158    public static final String MSG_KEY = "ws.preceded";
159
160    /** Control whether whitespace is allowed if the token is at a linebreak. */
161    private boolean allowLineBreaks;
162
163    @Override
164    public int[] getDefaultTokens() {
165        return new int[] {
166            TokenTypes.COMMA,
167            TokenTypes.SEMI,
168            TokenTypes.POST_INC,
169            TokenTypes.POST_DEC,
170            TokenTypes.ELLIPSIS,
171            TokenTypes.LABELED_STAT,
172        };
173    }
174
175    @Override
176    public int[] getAcceptableTokens() {
177        return new int[] {
178            TokenTypes.COMMA,
179            TokenTypes.SEMI,
180            TokenTypes.POST_INC,
181            TokenTypes.POST_DEC,
182            TokenTypes.DOT,
183            TokenTypes.GENERIC_START,
184            TokenTypes.GENERIC_END,
185            TokenTypes.ELLIPSIS,
186            TokenTypes.LABELED_STAT,
187            TokenTypes.METHOD_REF,
188        };
189    }
190
191    @Override
192    public int[] getRequiredTokens() {
193        return CommonUtil.EMPTY_INT_ARRAY;
194    }
195
196    @Override
197    public void visitToken(DetailAST ast) {
198        final String line = getLine(ast.getLineNo() - 1);
199        final int before = ast.getColumnNo() - 1;
200
201        if ((before == -1 || Character.isWhitespace(line.charAt(before)))
202                && !isInEmptyForInitializerOrCondition(ast)) {
203            boolean flag = !allowLineBreaks;
204            // verify all characters before '.' are whitespace
205            for (int i = 0; i <= before - 1; i++) {
206                if (!Character.isWhitespace(line.charAt(i))) {
207                    flag = true;
208                    break;
209                }
210            }
211            if (flag) {
212                log(ast, MSG_KEY, ast.getText());
213            }
214        }
215    }
216
217    /**
218     * Checks that semicolon is in empty for initializer or condition.
219     *
220     * @param semicolonAst DetailAST of semicolon.
221     * @return true if semicolon is in empty for initializer or condition.
222     */
223    private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
224        boolean result = false;
225        final DetailAST sibling = semicolonAst.getPreviousSibling();
226        if (sibling != null
227                && (sibling.getType() == TokenTypes.FOR_INIT
228                        || sibling.getType() == TokenTypes.FOR_CONDITION)
229                && !sibling.hasChildren()) {
230            result = true;
231        }
232        return result;
233    }
234
235    /**
236     * Setter to control whether whitespace is allowed if the token is at a linebreak.
237     *
238     * @param allowLineBreaks whether whitespace should be
239     *     flagged at line breaks.
240     */
241    public void setAllowLineBreaks(boolean allowLineBreaks) {
242        this.allowLineBreaks = allowLineBreaks;
243    }
244
245}