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.coding; 021 022import java.util.Objects; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028 029/** 030 * <p> 031 * Check that the {@code default} is after all the cases in a {@code switch} statement. 032 * </p> 033 * <p> 034 * Rationale: Java allows {@code default} anywhere within the 035 * {@code switch} statement. But it is more readable if it comes after the last {@code case}. 036 * </p> 037 * <ul> 038 * <li> 039 * Property {@code skipIfLastAndSharedWithCase} - Control whether to allow {@code default} 040 * along with {@code case} if they are not last. 041 * Type is {@code boolean}. 042 * Default value is {@code false}. 043 * </li> 044 * </ul> 045 * <p> 046 * To configure the check: 047 * </p> 048 * <pre> 049 * <module name="DefaultComesLast"/> 050 * </pre> 051 * <p>Example:</p> 052 * <pre> 053 * switch (i) { 054 * case 1: 055 * break; 056 * case 2: 057 * break; 058 * default: // OK 059 * break; 060 * } 061 * 062 * switch (i) { 063 * case 1: 064 * break; 065 * case 2: 066 * break; // OK, no default 067 * } 068 * 069 * switch (i) { 070 * case 1: 071 * break; 072 * default: // violation, 'default' before 'case' 073 * break; 074 * case 2: 075 * break; 076 * } 077 * 078 * switch (i) { 079 * case 1: 080 * default: // violation, 'default' before 'case' 081 * break; 082 * case 2: 083 * break; 084 * } 085 * </pre> 086 * <p>To configure the check to allow default label to be not last if it is shared with case: 087 * </p> 088 * <pre> 089 * <module name="DefaultComesLast"> 090 * <property name="skipIfLastAndSharedWithCase" value="true"/> 091 * </module> 092 * </pre> 093 * <p>Example:</p> 094 * <pre> 095 * switch (i) { 096 * case 1: 097 * break; 098 * case 2: 099 * default: // OK 100 * break; 101 * case 3: 102 * break; 103 * } 104 * 105 * switch (i) { 106 * case 1: 107 * break; 108 * default: // violation 109 * case 2: 110 * break; 111 * } 112 * 113 * // Switch rules are not subject to fall through, so this is still a violation: 114 * switch (i) { 115 * case 1 -> x = 9; 116 * default -> x = 10; // violation 117 * case 2 -> x = 32; 118 * } 119 * </pre> 120 * <p> 121 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 122 * </p> 123 * <p> 124 * Violation Message Keys: 125 * </p> 126 * <ul> 127 * <li> 128 * {@code default.comes.last} 129 * </li> 130 * <li> 131 * {@code default.comes.last.in.casegroup} 132 * </li> 133 * </ul> 134 * 135 * @since 3.4 136 */ 137@StatelessCheck 138public class DefaultComesLastCheck extends AbstractCheck { 139 140 /** 141 * A key is pointing to the warning message text in "messages.properties" 142 * file. 143 */ 144 public static final String MSG_KEY = "default.comes.last"; 145 146 /** 147 * A key is pointing to the warning message text in "messages.properties" 148 * file. 149 */ 150 public static final String MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE = 151 "default.comes.last.in.casegroup"; 152 153 /** Control whether to allow {@code default} along with {@code case} if they are not last. */ 154 private boolean skipIfLastAndSharedWithCase; 155 156 @Override 157 public int[] getAcceptableTokens() { 158 return getRequiredTokens(); 159 } 160 161 @Override 162 public int[] getDefaultTokens() { 163 return getRequiredTokens(); 164 } 165 166 @Override 167 public int[] getRequiredTokens() { 168 return new int[] { 169 TokenTypes.LITERAL_DEFAULT, 170 }; 171 } 172 173 /** 174 * Setter to control whether to allow {@code default} along with 175 * {@code case} if they are not last. 176 * 177 * @param newValue whether to ignore checking. 178 */ 179 public void setSkipIfLastAndSharedWithCase(boolean newValue) { 180 skipIfLastAndSharedWithCase = newValue; 181 } 182 183 @Override 184 public void visitToken(DetailAST ast) { 185 final DetailAST defaultGroupAST = ast.getParent(); 186 187 // Switch rules are not subject to fall through. 188 final boolean isSwitchRule = defaultGroupAST.getType() == TokenTypes.SWITCH_RULE; 189 190 if (skipIfLastAndSharedWithCase && !isSwitchRule) { 191 if (Objects.nonNull(findNextSibling(ast, TokenTypes.LITERAL_CASE))) { 192 log(ast, MSG_KEY_SKIP_IF_LAST_AND_SHARED_WITH_CASE); 193 } 194 else if (ast.getPreviousSibling() == null 195 && Objects.nonNull(findNextSibling(defaultGroupAST, 196 TokenTypes.CASE_GROUP))) { 197 log(ast, MSG_KEY); 198 } 199 } 200 else if (Objects.nonNull(findNextSibling(defaultGroupAST, 201 TokenTypes.CASE_GROUP)) 202 || Objects.nonNull(findNextSibling(defaultGroupAST, 203 TokenTypes.SWITCH_RULE))) { 204 log(ast, MSG_KEY); 205 } 206 } 207 208 /** 209 * Return token type only if passed tokenType in argument is found or returns -1. 210 * 211 * @param ast root node. 212 * @param tokenType tokentype to be processed. 213 * @return token if desired token is found or else null. 214 */ 215 private static DetailAST findNextSibling(DetailAST ast, int tokenType) { 216 DetailAST token = null; 217 DetailAST node = ast.getNextSibling(); 218 while (node != null) { 219 if (node.getType() == tokenType) { 220 token = node; 221 break; 222 } 223 node = node.getNextSibling(); 224 } 225 return token; 226 } 227 228}