@@ -160,6 +160,20 @@ enum CaseQualifications {
160160 SOME_OR_ALL_CASES_DONT_QUALIFY
161161 }
162162
163+ /**
164+ * The kind of null/default cases included within a single CaseTree.
165+ *
166+ * <p>This enum is used to classify whether a CaseTree includes a null and/or default. Referencing
167+ * JLS 21 §14.11.1, the `SwitchLabel:` production has specific rules applicable to null/default
168+ * cases: `case null, [default]` and `default`. All other scenarios are lumped into KIND_NEITHER.
169+ */
170+ enum NullDefaultKind {
171+ KIND_NULL_AND_DEFAULT ,
172+ KIND_DEFAULT ,
173+ KIND_NULL ,
174+ KIND_NEITHER
175+ }
176+
163177 private final boolean enableDirectConversion ;
164178 private final boolean enableReturnSwitchConversion ;
165179 private final boolean enableAssignmentSwitchConversion ;
@@ -293,12 +307,15 @@ private static AnalysisResult analyzeSwitchTree(SwitchTree switchTree, VisitorSt
293307 // Case patterns are not currently supported by the checker.
294308 return DEFAULT_ANALYSIS_RESULT ;
295309 }
296- boolean isDefaultCase = caseTree .getExpressions ().isEmpty ();
310+
311+ NullDefaultKind nullDefaultKind = analyzeCaseForNullAndDefault (caseTree );
312+ boolean isDefaultCase =
313+ nullDefaultKind .equals (NullDefaultKind .KIND_DEFAULT )
314+ || nullDefaultKind .equals (NullDefaultKind .KIND_NULL_AND_DEFAULT );
297315 isNullCase .set (
298316 caseIndex ,
299- !isDefaultCase
300- && caseTree .getExpressions ().stream ()
301- .anyMatch (expressionTree -> expressionTree .getKind () == Kind .NULL_LITERAL ));
317+ nullDefaultKind .equals (NullDefaultKind .KIND_NULL )
318+ || nullDefaultKind .equals (NullDefaultKind .KIND_NULL_AND_DEFAULT ));
302319 hasDefaultCase |= isDefaultCase ;
303320
304321 // Null case can never be grouped with a preceding case
@@ -606,6 +623,21 @@ private static String renderJavaSourceOfAssignment(ExpressionTree tree) {
606623 return pretty .operatorName (jcAssignOp .getTag ().noAssignOp ()) + EQUALS_STRING ;
607624 }
608625
626+ /**
627+ * Renders the Java source prefix needed for the supplied {@code nullDefaultKind}, incorporating
628+ * whether the `default` case should be removed.
629+ */
630+ private static String renderNullDefaultKindPrefix (
631+ NullDefaultKind nullDefaultKind , boolean removeDefault ) {
632+
633+ return switch (nullDefaultKind ) {
634+ case KIND_NULL_AND_DEFAULT -> removeDefault ? "case null" : "case null, default" ;
635+ case KIND_NULL -> "case null" ;
636+ case KIND_DEFAULT -> removeDefault ? "" : "default" ;
637+ case KIND_NEITHER -> "case " ;
638+ };
639+ }
640+
609641 /**
610642 * Analyze a single {@code case} of a single {@code switch} statement to determine whether it is
611643 * convertible to a return switch. The supplied {@code previousCaseQualifications} is updated and
@@ -951,10 +983,10 @@ private static SuggestedFix convertDirectlyToExpressionSwitch(
951983 boolean firstCaseInGroup = true ;
952984 for (int caseIndex = 0 ; caseIndex < cases .size (); caseIndex ++) {
953985 CaseTree caseTree = cases .get (caseIndex );
954- boolean isDefaultCase = isSwitchDefault (caseTree );
986+ NullDefaultKind nullDefaultKind = analyzeCaseForNullAndDefault (caseTree );
955987
956- if (removeDefault && isDefaultCase ) {
957- // Skip default case
988+ if (removeDefault && nullDefaultKind . equals ( NullDefaultKind . KIND_DEFAULT ) ) {
989+ // Skip removed default (and its code) entirely
958990 continue ;
959991 }
960992
@@ -971,14 +1003,19 @@ private static SuggestedFix convertDirectlyToExpressionSwitch(
9711003 ? extractCommentsBeforeFirstCase (switchTree , allSwitchComments ).orElse ("" )
9721004 : "" );
9731005
974- replacementCodeBuilder .append ("\n " );
975- if (!isDefaultCase ) {
976- replacementCodeBuilder .append ("case " );
1006+ replacementCodeBuilder
1007+ .append ("\n " )
1008+ .append (renderNullDefaultKindPrefix (nullDefaultKind , removeDefault ));
1009+ } else {
1010+ // Second or later case in our group
1011+ if (nullDefaultKind .equals (NullDefaultKind .KIND_DEFAULT )) {
1012+ replacementCodeBuilder .append ("default" );
9771013 }
9781014 }
979- replacementCodeBuilder .append (
980- isDefaultCase ? "default" : printCaseExpressions (caseTree , state ));
9811015
1016+ if (nullDefaultKind .equals (NullDefaultKind .KIND_NEITHER )) {
1017+ replacementCodeBuilder .append (printCaseExpressions (caseTree , state ));
1018+ }
9821019 Optional <String > commentsAfterCaseOptional =
9831020 extractCommentsAfterCase (switchTree , allSwitchComments , state , caseIndex );
9841021 if (analysisResult .groupedWithNextCase ().get (caseIndex )) {
@@ -1095,9 +1132,9 @@ private static SuggestedFix convertToReturnSwitch(
10951132 boolean firstCaseInGroup = true ;
10961133 for (int caseIndex = 0 ; caseIndex < cases .size (); caseIndex ++) {
10971134 CaseTree caseTree = cases .get (caseIndex );
1098- boolean isDefaultCase = isSwitchDefault (caseTree );
1099- if (removeDefault && isDefaultCase ) {
1100- // Skip default case
1135+ NullDefaultKind nullDefaultKind = analyzeCaseForNullAndDefault (caseTree );
1136+ if (removeDefault && nullDefaultKind . equals ( NullDefaultKind . KIND_DEFAULT ) ) {
1137+ // Skip removed default (and its code) entirely
11011138 continue ;
11021139 }
11031140
@@ -1111,13 +1148,19 @@ private static SuggestedFix convertToReturnSwitch(
11111148 ? extractCommentsBeforeFirstCase (switchTree , allSwitchComments ).orElse ("" )
11121149 : "" );
11131150
1114- replacementCodeBuilder .append ("\n " );
1115- if (!isDefaultCase ) {
1116- replacementCodeBuilder .append ("case " );
1151+ replacementCodeBuilder
1152+ .append ("\n " )
1153+ .append (renderNullDefaultKindPrefix (nullDefaultKind , removeDefault ));
1154+ } else {
1155+ // Second or later case in our group
1156+ if (nullDefaultKind .equals (NullDefaultKind .KIND_DEFAULT )) {
1157+ replacementCodeBuilder .append ("default" );
11171158 }
11181159 }
1119- replacementCodeBuilder .append (
1120- isDefaultCase ? "default" : printCaseExpressions (caseTree , state ));
1160+
1161+ if (nullDefaultKind .equals (NullDefaultKind .KIND_NEITHER )) {
1162+ replacementCodeBuilder .append (printCaseExpressions (caseTree , state ));
1163+ }
11211164
11221165 Optional <String > commentsAfterCaseOptional =
11231166 extractCommentsAfterCase (switchTree , allSwitchComments , state , caseIndex );
@@ -1323,9 +1366,10 @@ private static SuggestedFix convertToAssignmentSwitch(
13231366 boolean firstCaseInGroup = true ;
13241367 for (int caseIndex = 0 ; caseIndex < cases .size (); caseIndex ++) {
13251368 CaseTree caseTree = cases .get (caseIndex );
1326- boolean isDefaultCase = isSwitchDefault (caseTree );
1327- if (removeDefault && isDefaultCase ) {
1328- // Remove `default:` case (and its code, if any) from the SuggestedFix
1369+ NullDefaultKind nullDefaultKind = analyzeCaseForNullAndDefault (caseTree );
1370+
1371+ if (removeDefault && nullDefaultKind .equals (NullDefaultKind .KIND_DEFAULT )) {
1372+ // Skip removed default (and its code) entirely
13291373 continue ;
13301374 }
13311375 ImmutableList <StatementTree > filteredStatements = filterOutRedundantBreak (caseTree );
@@ -1340,13 +1384,19 @@ private static SuggestedFix convertToAssignmentSwitch(
13401384 ? extractCommentsBeforeFirstCase (switchTree , allSwitchComments ).orElse ("" )
13411385 : "" );
13421386
1343- replacementCodeBuilder .append ("\n " );
1344- if (!isDefaultCase ) {
1345- replacementCodeBuilder .append ("case " );
1387+ replacementCodeBuilder
1388+ .append ("\n " )
1389+ .append (renderNullDefaultKindPrefix (nullDefaultKind , removeDefault ));
1390+ } else {
1391+ // Second or later case in our group
1392+ if (nullDefaultKind .equals (NullDefaultKind .KIND_DEFAULT )) {
1393+ replacementCodeBuilder .append ("default" );
13461394 }
13471395 }
1348- replacementCodeBuilder .append (
1349- isDefaultCase ? "default" : printCaseExpressions (caseTree , state ));
1396+
1397+ if (nullDefaultKind .equals (NullDefaultKind .KIND_NEITHER )) {
1398+ replacementCodeBuilder .append (printCaseExpressions (caseTree , state ));
1399+ }
13501400
13511401 Optional <String > commentsAfterCaseOptional =
13521402 extractCommentsAfterCase (switchTree , allSwitchComments , state , caseIndex );
@@ -1778,6 +1828,26 @@ private static String transformAssignOrThrowBlock(
17781828 return transformedBlockBuilder .toString ();
17791829 }
17801830
1831+ /**
1832+ * Determines whether the supplied {@code caseTree} case contains `case null` and/or `default`.
1833+ */
1834+ private static NullDefaultKind analyzeCaseForNullAndDefault (CaseTree caseTree ) {
1835+ boolean hasDefault = isSwitchDefault (caseTree );
1836+ boolean hasNull =
1837+ caseTree .getExpressions ().stream ()
1838+ .anyMatch (expression -> expression .getKind ().equals (Tree .Kind .NULL_LITERAL ));
1839+
1840+ if (hasNull && hasDefault ) {
1841+ return NullDefaultKind .KIND_NULL_AND_DEFAULT ;
1842+ } else if (hasNull ) {
1843+ return NullDefaultKind .KIND_NULL ;
1844+ } else if (hasDefault ) {
1845+ return NullDefaultKind .KIND_DEFAULT ;
1846+ }
1847+
1848+ return NullDefaultKind .KIND_NEITHER ;
1849+ }
1850+
17811851 @ AutoValue
17821852 abstract static class AnalysisResult {
17831853 /** Whether the statement switch can be directly converted to an expression switch */
0 commit comments