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}