001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 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 switch statement has a {@code default} clause. 031 * </p> 032 * <p> 033 * Rationale: It's usually a good idea to introduce a 034 * default case in every switch statement. Even if 035 * the developer is sure that all currently possible 036 * cases are covered, this should be expressed in the 037 * default branch, e.g. by using an assertion. This way 038 * the code is protected against later changes, e.g. 039 * introduction of new types in an enumeration type. 040 * </p> 041 * <p> 042 * This check does not validate any switch expressions. Rationale: 043 * The compiler requires switch expressions to be exhaustive. This means 044 * that all possible inputs must be covered. 045 * </p> 046 * <p> 047 * This check does not validate switch statements that use pattern or null 048 * labels. Rationale: Switch statements that use pattern or null labels are 049 * checked by the compiler for exhaustiveness. This means that all possible 050 * inputs must be covered. 051 * </p> 052 * <p> 053 * See the <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.28"> 054 * Java Language Specification</a> for more information about switch statements 055 * and expressions. 056 * </p> 057 * <p> 058 * To configure the check: 059 * </p> 060 * <pre> 061 * <module name="MissingSwitchDefault"/> 062 * </pre> 063 * <p>Example of violation:</p> 064 * <pre> 065 * switch (i) { // violation 066 * case 1: 067 * break; 068 * case 2: 069 * break; 070 * } 071 * </pre> 072 * <p>Example of correct code:</p> 073 * <pre> 074 * switch (i) { 075 * case 1: 076 * break; 077 * case 2: 078 * break; 079 * default: // OK 080 * break; 081 * } 082 * switch (o) { 083 * case String s: // type pattern 084 * System.out.println(s); 085 * break; 086 * case Integer i: // type pattern 087 * System.out.println("Integer"); 088 * break; 089 * default: // will not compile without default label, thanks to type pattern label usage 090 * break; 091 * } 092 * </pre> 093 * <p>Example of correct code which does not require default labels:</p> 094 * <pre> 095 * sealed interface S permits A, B, C {} 096 * final class A implements S {} 097 * final class B implements S {} 098 * record C(int i) implements S {} // Implicitly final 099 * 100 * /** 101 * * The completeness of a switch statement can be 102 * * determined by the contents of the permits clause 103 * * of interface S. No default label or default case 104 * * label is allowed by the compiler in this situation, so 105 * * this check does not enforce a default label in such 106 * * statements. 107 * */ 108 * static void showSealedCompleteness(S s) { 109 * switch (s) { 110 * case A a: System.out.println("A"); break; 111 * case B b: System.out.println("B"); break; 112 * case C c: System.out.println("C"); break; 113 * } 114 * } 115 * 116 * /** 117 * * A total type pattern matches all possible inputs, 118 * * including null. A default label or 119 * * default case is not allowed by the compiler in this 120 * * situation. Accordingly, check does not enforce a 121 * * default label in this case. 122 * */ 123 * static void showTotality(String s) { 124 * switch (s) { 125 * case Object o: // total type pattern 126 * System.out.println("o!"); 127 * } 128 * } 129 * 130 * enum Color { RED, GREEN, BLUE } 131 * 132 * static int showSwitchExpressionExhaustiveness(Color color) { 133 * switch (color) { 134 * case RED: System.out.println("RED"); break; 135 * case BLUE: System.out.println("BLUE"); break; 136 * case GREEN: System.out.println("GREEN"); break; 137 * // Check will require default label below, compiler 138 * // does not enforce a switch statement with constants 139 * // to be complete. 140 * default: System.out.println("Something else"); 141 * } 142 * 143 * // Check will not require default label in switch 144 * // expression below, because code will not compile 145 * // if all possible values are not handled. If the 146 * // 'Color' enum is extended, code will fail to compile. 147 * return switch (color) { 148 * case RED: 149 * yield 1; 150 * case GREEN: 151 * yield 2; 152 * case BLUE: 153 * yield 3; 154 * }; 155 * } 156 * </pre> 157 * <p> 158 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 159 * </p> 160 * <p> 161 * Violation Message Keys: 162 * </p> 163 * <ul> 164 * <li> 165 * {@code missing.switch.default} 166 * </li> 167 * </ul> 168 * 169 * @since 3.1 170 */ 171@StatelessCheck 172public class MissingSwitchDefaultCheck extends AbstractCheck { 173 174 /** 175 * A key is pointing to the warning message text in "messages.properties" 176 * file. 177 */ 178 public static final String MSG_KEY = "missing.switch.default"; 179 180 @Override 181 public int[] getDefaultTokens() { 182 return getRequiredTokens(); 183 } 184 185 @Override 186 public int[] getAcceptableTokens() { 187 return getRequiredTokens(); 188 } 189 190 @Override 191 public int[] getRequiredTokens() { 192 return new int[] {TokenTypes.LITERAL_SWITCH}; 193 } 194 195 @Override 196 public void visitToken(DetailAST ast) { 197 if (!containsDefaultLabel(ast) 198 && !containsPatternCaseLabelElement(ast) 199 && !containsDefaultCaseLabelElement(ast) 200 && !isSwitchExpression(ast)) { 201 log(ast, MSG_KEY); 202 } 203 } 204 205 /** 206 * Checks if the case group or its sibling contain the 'default' switch. 207 * 208 * @param detailAst first case group to check. 209 * @return true if 'default' switch found. 210 */ 211 private static boolean containsDefaultLabel(DetailAST detailAst) { 212 return TokenUtil.findFirstTokenByPredicate(detailAst, 213 ast -> ast.findFirstToken(TokenTypes.LITERAL_DEFAULT) != null 214 ).isPresent(); 215 } 216 217 /** 218 * Checks if a switch block contains a case label with a pattern variable definition. 219 * In this situation, the compiler enforces the given switch block to cover 220 * all possible inputs, and we do not need a default label. 221 * 222 * @param detailAst first case group to check. 223 * @return true if switch block contains a pattern case label element 224 */ 225 private static boolean containsPatternCaseLabelElement(DetailAST detailAst) { 226 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 227 return ast.getFirstChild() != null 228 && ast.getFirstChild().findFirstToken(TokenTypes.PATTERN_VARIABLE_DEF) != null; 229 }).isPresent(); 230 } 231 232 /** 233 * Checks if a switch block contains a default case label. 234 * 235 * @param detailAst first case group to check. 236 * @return true if switch block contains default case label 237 */ 238 private static boolean containsDefaultCaseLabelElement(DetailAST detailAst) { 239 return TokenUtil.findFirstTokenByPredicate(detailAst, ast -> { 240 return ast.getFirstChild() != null 241 && ast.getFirstChild().findFirstToken(TokenTypes.LITERAL_DEFAULT) != null; 242 }).isPresent(); 243 } 244 245 /** 246 * Checks if this LITERAL_SWITCH token is part of a switch expression. 247 * 248 * @param ast the switch statement we are checking 249 * @return true if part of a switch expression 250 */ 251 private static boolean isSwitchExpression(DetailAST ast) { 252 return ast.getParent().getType() == TokenTypes.EXPR 253 || ast.getParent().getParent().getType() == TokenTypes.EXPR; 254 } 255}