001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.TokenUtil;
027
028/**
029 * <p>
030 * Checks that there is no whitespace before the colon in a switch block.
031 * </p>
032 * <p>
033 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
034 * </p>
035 * <p>
036 * Violation Message Keys:
037 * </p>
038 * <ul>
039 * <li>
040 * {@code ws.preceded}
041 * </li>
042 * </ul>
043 *
044 * @since 8.45
045 */
046@StatelessCheck
047public class NoWhitespaceBeforeCaseDefaultColonCheck
048    extends AbstractCheck {
049
050    /**
051     * A key is pointing to the warning message text in "messages.properties"
052     * file.
053     */
054    public static final String MSG_KEY = "ws.preceded";
055
056    @Override
057    public int[] getDefaultTokens() {
058        return getRequiredTokens();
059    }
060
061    @Override
062    public int[] getAcceptableTokens() {
063        return getRequiredTokens();
064    }
065
066    @Override
067    public int[] getRequiredTokens() {
068        return new int[] {TokenTypes.COLON};
069    }
070
071    @Override
072    public boolean isCommentNodesRequired() {
073        return true;
074    }
075
076    @Override
077    public void visitToken(DetailAST ast) {
078        if (isInSwitch(ast) && isWhiteSpaceBeforeColon(ast)) {
079            log(ast, MSG_KEY, ast.getText());
080        }
081    }
082
083    /**
084     * Checks if the colon is inside a switch block.
085     *
086     * @param colonAst DetailAST to check.
087     * @return true, if colon case is inside a switch block.
088     */
089    private static boolean isInSwitch(DetailAST colonAst) {
090        return TokenUtil.isOfType(colonAst.getParent(), TokenTypes.LITERAL_CASE,
091                TokenTypes.LITERAL_DEFAULT);
092    }
093
094    /**
095     * Checks if there is a whitespace before the colon of a switch case or switch default.
096     *
097     * @param colonAst DetailAST to check.
098     * @return true, if there is whitespace preceding colonAst.
099     */
100    private static boolean isWhiteSpaceBeforeColon(DetailAST colonAst) {
101        final DetailAST parent = colonAst.getParent();
102        final boolean result;
103        if (isOnDifferentLineWithPreviousToken(colonAst)) {
104            result = true;
105        }
106        else if (parent.getType() == TokenTypes.LITERAL_CASE) {
107            result = isWhitespaceBeforeColonOfCase(colonAst);
108        }
109        else {
110            result = isWhitespaceBeforeColonOfDefault(colonAst);
111        }
112        return result;
113    }
114
115    /**
116     * Checks if there is a whitespace before the colon of a switch case.
117     *
118     * @param colonAst DetailAST to check.
119     * @return true, if there is whitespace preceding colonAst.
120     */
121    private static boolean isWhitespaceBeforeColonOfCase(DetailAST colonAst) {
122        final DetailAST previousSibling = colonAst.getPreviousSibling();
123        int offset = 0;
124        if (previousSibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
125            offset = 1;
126        }
127        return colonAst.getColumnNo() != getLastColumnNumberOf(previousSibling) + offset;
128    }
129
130    /**
131     * Checks if there is a whitespace before the colon of a switch default.
132     *
133     * @param colonAst DetailAST to check.
134     * @return true, if there is whitespace preceding colonAst.
135     */
136    private static boolean isWhitespaceBeforeColonOfDefault(DetailAST colonAst) {
137        final boolean result;
138        final DetailAST previousSibling = colonAst.getPreviousSibling();
139        if (previousSibling == null) {
140            final DetailAST literalDefault = colonAst.getParent();
141            result = colonAst.getColumnNo()
142                    != literalDefault.getColumnNo() + literalDefault.getText().length();
143        }
144        else {
145            result =
146                    colonAst.getColumnNo() != getLastColumnNumberOf(previousSibling) + 1;
147        }
148        return result;
149    }
150
151    /**
152     * Checks if the colon is on same line as of case or default.
153     *
154     * @param colonAst DetailAST to check.
155     * @return true, if colon case is in different line as of case or default.
156     */
157    private static boolean isOnDifferentLineWithPreviousToken(DetailAST colonAst) {
158        final DetailAST previousSibling;
159        final DetailAST parent = colonAst.getParent();
160        if (parent.getType() == TokenTypes.LITERAL_CASE) {
161            previousSibling = colonAst.getPreviousSibling();
162        }
163        else {
164            previousSibling = colonAst.getParent();
165        }
166        return !TokenUtil.areOnSameLine(previousSibling, colonAst);
167    }
168
169    /**
170     * Returns the last column number of an ast.
171     *
172     * @param ast DetailAST to check.
173     * @return ast's last column number.
174     */
175    private static int getLastColumnNumberOf(DetailAST ast) {
176        DetailAST lastChild = ast;
177        while (lastChild.hasChildren()) {
178            lastChild = lastChild.getLastChild();
179        }
180        return lastChild.getColumnNo() + lastChild.getText().length();
181    }
182
183}