001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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.CodePointUtil;
027import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
028
029/**
030 * <div>
031 * Checks that there is no whitespace before a token.
032 * More specifically, it checks that it is not preceded with whitespace,
033 * or (if linebreaks are allowed) all characters on the line before are
034 * whitespace. To allow linebreaks before a token, set property
035 * {@code allowLineBreaks} to {@code true}. No check occurs before semicolons in empty
036 * for loop initializers or conditions.
037 * </div>
038 *
039 * @since 3.0
040 */
041@StatelessCheck
042public class NoWhitespaceBeforeCheck
043    extends AbstractCheck {
044
045    /**
046     * A key is pointing to the warning message text in "messages.properties"
047     * file.
048     */
049    public static final String MSG_KEY = "ws.preceded";
050
051    /** Control whether whitespace is allowed if the token is at a linebreak. */
052    private boolean allowLineBreaks;
053
054    /**
055     * Creates a new {@code NoWhitespaceBeforeCheck} instance.
056     */
057    public NoWhitespaceBeforeCheck() {
058        // no code by default
059    }
060
061    @Override
062    public int[] getDefaultTokens() {
063        return new int[] {
064            TokenTypes.COMMA,
065            TokenTypes.SEMI,
066            TokenTypes.POST_INC,
067            TokenTypes.POST_DEC,
068            TokenTypes.ELLIPSIS,
069            TokenTypes.LABELED_STAT,
070        };
071    }
072
073    @Override
074    public int[] getAcceptableTokens() {
075        return new int[] {
076            TokenTypes.COMMA,
077            TokenTypes.SEMI,
078            TokenTypes.POST_INC,
079            TokenTypes.POST_DEC,
080            TokenTypes.DOT,
081            TokenTypes.GENERIC_START,
082            TokenTypes.GENERIC_END,
083            TokenTypes.ELLIPSIS,
084            TokenTypes.LABELED_STAT,
085            TokenTypes.METHOD_REF,
086        };
087    }
088
089    @Override
090    public int[] getRequiredTokens() {
091        return CommonUtil.EMPTY_INT_ARRAY;
092    }
093
094    @Override
095    public void visitToken(DetailAST ast) {
096        final int[] line = getLineCodePoints(ast.getLineNo() - 1);
097        final int columnNoBeforeToken = ast.getColumnNo() - 1;
098        final boolean isFirstToken = columnNoBeforeToken == -1;
099
100        if ((isFirstToken || CommonUtil.isCodePointWhitespace(line, columnNoBeforeToken))
101                && !isInEmptyForInitializerOrCondition(ast)) {
102            final boolean isViolation = !allowLineBreaks
103                    || !isFirstToken
104                    && !CodePointUtil.hasWhitespaceBefore(columnNoBeforeToken, line);
105
106            if (isViolation) {
107                log(ast, MSG_KEY, ast.getText());
108            }
109        }
110    }
111
112    /**
113     * Checks that semicolon is in empty for initializer or condition.
114     *
115     * @param semicolonAst DetailAST of semicolon.
116     * @return true if semicolon is in empty for initializer or condition.
117     */
118    private static boolean isInEmptyForInitializerOrCondition(DetailAST semicolonAst) {
119        boolean result = false;
120        final DetailAST sibling = semicolonAst.getPreviousSibling();
121        if (sibling != null
122                && (sibling.getType() == TokenTypes.FOR_INIT
123                        || sibling.getType() == TokenTypes.FOR_CONDITION)
124                && !sibling.hasChildren()) {
125            result = true;
126        }
127        return result;
128    }
129
130    /**
131     * Setter to control whether whitespace is allowed if the token is at a linebreak.
132     *
133     * @param allowLineBreaks whether whitespace should be
134     *     flagged at line breaks.
135     * @since 3.0
136     */
137    public void setAllowLineBreaks(boolean allowLineBreaks) {
138        this.allowLineBreaks = allowLineBreaks;
139    }
140
141}