001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 /** 094 * Creates a new {@code MissingSwitchDefaultCheck} instance. 095 */ 096 public MissingSwitchDefaultCheck() { 097 // no code by default 098 } 099 100 @Override 101 public int[] getDefaultTokens() { 102 return getRequiredTokens(); 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return getRequiredTokens(); 108 } 109 110 @Override 111 public int[] getRequiredTokens() { 112 return new int[] {TokenTypes.LITERAL_SWITCH}; 113 } 114 115 @Override 116 public void visitToken(DetailAST ast) { 117 if (!containsDefaultLabel(ast) 118 && !containsPatternCaseLabelElement(ast) 119 && !containsNullCaseLabelElement(ast) 120 && !isSwitchExpression(ast)) { 121 log(ast, MSG_KEY); 122 } 123 } 124 125 /** 126 * Checks if the case group or its sibling contain the 'default' switch. 127 * 128 * @param detailAst first case group to check. 129 * @return true if 'default' switch found. 130 */ 131 private static boolean containsDefaultLabel(DetailAST detailAst) { 132 return TokenUtil.findFirstTokenByPredicate(detailAst, 133 ast -> ast.findFirstToken(TokenTypes.LITERAL_DEFAULT) != null 134 ).isPresent(); 135 } 136 137 /** 138 * Checks if a switch block contains a case label with a pattern variable definition 139 * or record pattern definition. 140 * In this situation, the compiler enforces the given switch block to cover 141 * all possible inputs, and we do not need a default label. 142 * 143 * @param detailAst first case group to check. 144 * @return true if switch block contains a pattern case label element 145 */ 146 private static boolean containsPatternCaseLabelElement(DetailAST detailAst) { 147 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 148 return ast.getFirstChild() != null 149 && (ast.getFirstChild().findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF) != null 150 || ast.getFirstChild().findFirstToken(TokenTypes.RECORD_PATTERN_DEF) != null); 151 }).isPresent(); 152 } 153 154 /** 155 * Checks if a switch block contains a null case label. 156 * 157 * @param detailAst first case group to check. 158 * @return true if switch block contains null case label 159 */ 160 private static boolean containsNullCaseLabelElement(DetailAST detailAst) { 161 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 162 return ast.getFirstChild() != null 163 && hasNullCaseLabel(ast.getFirstChild()); 164 }).isPresent(); 165 } 166 167 /** 168 * Checks if this LITERAL_SWITCH token is part of a switch expression. 169 * 170 * @param ast the switch statement we are checking 171 * @return true if part of a switch expression 172 */ 173 private static boolean isSwitchExpression(DetailAST ast) { 174 return !TokenUtil.isOfType(ast.getParent().getType(), SWITCH_STATEMENT_PARENTS); 175 } 176 177 /** 178 * Checks if the case contains null label. 179 * 180 * @param detailAST the switch statement we are checking 181 * @return returnValue the ast of null label 182 */ 183 private static boolean hasNullCaseLabel(DetailAST detailAST) { 184 return TokenUtil.findFirstTokenByPredicate(detailAST.getParent(), ast -> { 185 final DetailAST expr = ast.findFirstToken(TokenTypes.EXPR); 186 return expr != null && expr.findFirstToken(TokenTypes.LITERAL_NULL) != null; 187 }).isPresent(); 188 } 189 190}