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.coding; 021 022import java.util.Set; 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; 028import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 029 030/** 031 * <div> 032 * Checks that switch statement has a {@code default} clause. 033 * </div> 034 * 035 * <p> 036 * Rationale: It's usually a good idea to introduce a 037 * default case in every switch statement. Even if 038 * the developer is sure that all currently possible 039 * cases are covered, this should be expressed in the 040 * default branch, e.g. by using an assertion. This way 041 * the code is protected against later changes, e.g. 042 * introduction of new types in an enumeration type. 043 * </p> 044 * 045 * <p> 046 * This check does not validate any switch expressions. Rationale: 047 * The compiler requires switch expressions to be exhaustive. This means 048 * that all possible inputs must be covered. 049 * </p> 050 * 051 * <p> 052 * This check does not validate switch statements that use pattern or null 053 * labels. Rationale: Switch statements that use pattern or null labels are 054 * checked by the compiler for exhaustiveness. This means that all possible 055 * inputs must be covered. 056 * </p> 057 * 058 * <p> 059 * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-15.html#jls-15.28"> 060 * Java Language Specification</a> for more information about switch statements 061 * and expressions. 062 * </p> 063 * 064 * <p> 065 * See the <a href="https://docs.oracle.com/javase/specs/jls/se22/html/jls-14.html#jls-14.30"> 066 * Java Language Specification</a> for more information about patterns. 067 * </p> 068 * 069 * <p> 070 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 071 * </p> 072 * 073 * <p> 074 * Violation Message Keys: 075 * </p> 076 * <ul> 077 * <li> 078 * {@code missing.switch.default} 079 * </li> 080 * </ul> 081 * 082 * @since 3.1 083 */ 084@StatelessCheck 085public class MissingSwitchDefaultCheck 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 = "missing.switch.default"; 092 093 /** 094 * Represents the possible parent tokens of a switch statement. 095 */ 096 private static final Set<Integer> SWITCH_STATEMENT_PARENTS = Set.of( 097 TokenTypes.SLIST, 098 TokenTypes.LITERAL_IF, 099 TokenTypes.LITERAL_ELSE, 100 TokenTypes.LITERAL_DO, 101 TokenTypes.LITERAL_WHILE, 102 TokenTypes.LITERAL_FOR, 103 TokenTypes.LABELED_STAT 104 ); 105 106 @Override 107 public int[] getDefaultTokens() { 108 return getRequiredTokens(); 109 } 110 111 @Override 112 public int[] getAcceptableTokens() { 113 return getRequiredTokens(); 114 } 115 116 @Override 117 public int[] getRequiredTokens() { 118 return new int[] {TokenTypes.LITERAL_SWITCH}; 119 } 120 121 @Override 122 public void visitToken(DetailAST ast) { 123 if (!containsDefaultLabel(ast) 124 && !containsPatternCaseLabelElement(ast) 125 && !containsDefaultCaseLabelElement(ast) 126 && !containsNullCaseLabelElement(ast) 127 && !isSwitchExpression(ast)) { 128 log(ast, MSG_KEY); 129 } 130 } 131 132 /** 133 * Checks if the case group or its sibling contain the 'default' switch. 134 * 135 * @param detailAst first case group to check. 136 * @return true if 'default' switch found. 137 */ 138 private static boolean containsDefaultLabel(DetailAST detailAst) { 139 return TokenUtil.findFirstTokenByPredicate(detailAst, 140 ast -> ast.findFirstToken(TokenTypes.LITERAL_DEFAULT) != null 141 ).isPresent(); 142 } 143 144 /** 145 * Checks if a switch block contains a case label with a pattern variable definition 146 * or record pattern definition. 147 * In this situation, the compiler enforces the given switch block to cover 148 * all possible inputs, and we do not need a default label. 149 * 150 * @param detailAst first case group to check. 151 * @return true if switch block contains a pattern case label element 152 */ 153 private static boolean containsPatternCaseLabelElement(DetailAST detailAst) { 154 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 155 return ast.getFirstChild() != null 156 && (ast.getFirstChild().findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF) != null 157 || ast.getFirstChild().findFirstToken(TokenTypes.RECORD_PATTERN_DEF) != null); 158 }).isPresent(); 159 } 160 161 /** 162 * Checks if a switch block contains a default case label. 163 * 164 * @param detailAst first case group to check. 165 * @return true if switch block contains default case label 166 */ 167 private static boolean containsDefaultCaseLabelElement(DetailAST detailAst) { 168 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 169 return ast.getFirstChild() != null 170 && ast.getFirstChild().findFirstToken(TokenTypes.LITERAL_DEFAULT) != null; 171 }).isPresent(); 172 } 173 174 /** 175 * Checks if a switch block contains a null case label. 176 * 177 * @param detailAst first case group to check. 178 * @return true if switch block contains null case label 179 */ 180 private static boolean containsNullCaseLabelElement(DetailAST detailAst) { 181 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 182 return ast.getFirstChild() != null 183 && hasNullCaseLabel(ast.getFirstChild()); 184 }).isPresent(); 185 } 186 187 /** 188 * Checks if this LITERAL_SWITCH token is part of a switch expression. 189 * 190 * @param ast the switch statement we are checking 191 * @return true if part of a switch expression 192 */ 193 private static boolean isSwitchExpression(DetailAST ast) { 194 return !TokenUtil.isOfType(ast.getParent().getType(), SWITCH_STATEMENT_PARENTS); 195 } 196 197 /** 198 * Checks if the case contains null label. 199 * 200 * @param detailAST the switch statement we are checking 201 * @return returnValue the ast of null label 202 */ 203 private static boolean hasNullCaseLabel(DetailAST detailAST) { 204 return TokenUtil.findFirstTokenByPredicate(detailAST.getParent(), ast -> { 205 final DetailAST expr = ast.findFirstToken(TokenTypes.EXPR); 206 return expr != null && expr.findFirstToken(TokenTypes.LITERAL_NULL) != null; 207 }).isPresent(); 208 } 209}