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