001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * @since 3.1 070 */ 071@StatelessCheck 072public class MissingSwitchDefaultCheck extends AbstractCheck { 073 074 /** 075 * A key is pointing to the warning message text in "messages.properties" 076 * file. 077 */ 078 public static final String MSG_KEY = "missing.switch.default"; 079 080 /** 081 * Represents the possible parent tokens of a switch statement. 082 */ 083 private static final Set<Integer> SWITCH_STATEMENT_PARENTS = Set.of( 084 TokenTypes.SLIST, 085 TokenTypes.LITERAL_IF, 086 TokenTypes.LITERAL_ELSE, 087 TokenTypes.LITERAL_DO, 088 TokenTypes.LITERAL_WHILE, 089 TokenTypes.LITERAL_FOR, 090 TokenTypes.LABELED_STAT 091 ); 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.LITERAL_SWITCH}; 106 } 107 108 @Override 109 public void visitToken(DetailAST ast) { 110 if (!containsDefaultLabel(ast) 111 && !containsPatternCaseLabelElement(ast) 112 && !containsDefaultCaseLabelElement(ast) 113 && !containsNullCaseLabelElement(ast) 114 && !isSwitchExpression(ast)) { 115 log(ast, MSG_KEY); 116 } 117 } 118 119 /** 120 * Checks if the case group or its sibling contain the 'default' switch. 121 * 122 * @param detailAst first case group to check. 123 * @return true if 'default' switch found. 124 */ 125 private static boolean containsDefaultLabel(DetailAST detailAst) { 126 return TokenUtil.findFirstTokenByPredicate(detailAst, 127 ast -> ast.findFirstToken(TokenTypes.LITERAL_DEFAULT) != null 128 ).isPresent(); 129 } 130 131 /** 132 * Checks if a switch block contains a case label with a pattern variable definition 133 * or record pattern definition. 134 * In this situation, the compiler enforces the given switch block to cover 135 * all possible inputs, and we do not need a default label. 136 * 137 * @param detailAst first case group to check. 138 * @return true if switch block contains a pattern case label element 139 */ 140 private static boolean containsPatternCaseLabelElement(DetailAST detailAst) { 141 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 142 return ast.getFirstChild() != null 143 && (ast.getFirstChild().findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF) != null 144 || ast.getFirstChild().findFirstToken(TokenTypes.RECORD_PATTERN_DEF) != null); 145 }).isPresent(); 146 } 147 148 /** 149 * Checks if a switch block contains a default case label. 150 * 151 * @param detailAst first case group to check. 152 * @return true if switch block contains default case label 153 */ 154 private static boolean containsDefaultCaseLabelElement(DetailAST detailAst) { 155 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 156 return ast.getFirstChild() != null 157 && ast.getFirstChild().findFirstToken(TokenTypes.LITERAL_DEFAULT) != null; 158 }).isPresent(); 159 } 160 161 /** 162 * Checks if a switch block contains a null case label. 163 * 164 * @param detailAst first case group to check. 165 * @return true if switch block contains null case label 166 */ 167 private static boolean containsNullCaseLabelElement(DetailAST detailAst) { 168 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 169 return ast.getFirstChild() != null 170 && hasNullCaseLabel(ast.getFirstChild()); 171 }).isPresent(); 172 } 173 174 /** 175 * Checks if this LITERAL_SWITCH token is part of a switch expression. 176 * 177 * @param ast the switch statement we are checking 178 * @return true if part of a switch expression 179 */ 180 private static boolean isSwitchExpression(DetailAST ast) { 181 return !TokenUtil.isOfType(ast.getParent().getType(), SWITCH_STATEMENT_PARENTS); 182 } 183 184 /** 185 * Checks if the case contains null label. 186 * 187 * @param detailAST the switch statement we are checking 188 * @return returnValue the ast of null label 189 */ 190 private static boolean hasNullCaseLabel(DetailAST detailAST) { 191 return TokenUtil.findFirstTokenByPredicate(detailAST.getParent(), ast -> { 192 final DetailAST expr = ast.findFirstToken(TokenTypes.EXPR); 193 return expr != null && expr.findFirstToken(TokenTypes.LITERAL_NULL) != null; 194 }).isPresent(); 195 } 196}