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