• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 662

15 Jul 2026 09:50AM UTC coverage: 79.207% (+0.02%) from 79.192%
662

push

github

web-flow
chore: Bump maven from 3.9.14 to 3.9.16 (#6872)

19211 of 25189 branches covered (76.27%)

Branch coverage included in aggregate %.

41648 of 51646 relevant lines covered (80.64%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.01
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLike.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.ast;
6

7
import java.util.HashSet;
8
import java.util.Iterator;
9
import java.util.Set;
10
import java.util.stream.Collectors;
11

12
import net.sourceforge.pmd.lang.ast.NodeStream;
13
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
14
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
15
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
16

17

18
/**
19
 * Common supertype for {@linkplain ASTSwitchStatement switch statements}
20
 * and {@linkplain ASTSwitchExpression switch expressions}. Their grammar
21
 * is identical, and is described below. The difference is that switch
22
 * expressions need to be exhaustive.
23
 *
24
 * <pre class="grammar">
25
 *
26
 * SwitchLike        ::= {@link ASTSwitchExpression SwitchExpression}
27
 *                     | {@link ASTSwitchStatement SwitchStatement}
28
 *
29
 *                   ::= "switch" "(" {@link ASTExpression Expression} ")" SwitchBlock
30
 *
31
 * SwitchBlock       ::= SwitchArrowBlock | SwitchNormalBlock
32
 *
33
 * SwitchArrowBlock  ::= "{" {@link ASTSwitchArrowBranch SwitchArrowBranch}* "}"
34
 * SwitchNormalBlock ::= "{" {@linkplain ASTSwitchFallthroughBranch SwitchFallthroughBranch}* "}"
35
 *
36
 * </pre>
37
 */
38
public interface ASTSwitchLike extends JavaNode, Iterable<ASTSwitchBranch> {
39

40
    /**
41
     * Returns true if this switch has a {@code default} case.
42
     */
43
    default boolean hasDefaultCase() {
44
        return getBranches().any(it -> it.getLabel().isDefault());
1✔
45
    }
46

47
    /**
48
     * Returns the default branch if this switch has a {@code default} case, {@code null} if not.
49
     * @since 7.27.0
50
     */
51
    default ASTSwitchBranch getDefaultCase() {
52
        return getBranches().first(it -> it.getLabel().isDefault());
1✔
53
    }
54

55

56
    /**
57
     * Returns a stream of all branches of this switch.
58
     */
59
    default NodeStream<ASTSwitchBranch> getBranches() {
60
        return children(ASTSwitchBranch.class);
1✔
61
    }
62

63

64
    /**
65
     * Gets the expression tested by this switch.
66
     * This is the expression between the parentheses.
67
     */
68
    default ASTExpression getTestedExpression() {
69
        return (ASTExpression) getChild(0);
1✔
70
    }
71

72

73
    /**
74
     * Returns true if this switch block tests an expression
75
     * having an enum type and all the constants of this type
76
     * are covered by a switch case. Returns false if the type of
77
     * the tested expression could not be resolved.
78
     */
79
    default boolean isExhaustiveEnumSwitch() {
80
        JTypeDeclSymbol symbol = getTestedExpression().getTypeMirror().getSymbol();
1✔
81
        if (symbol instanceof JClassSymbol && ((JClassSymbol) symbol).isEnum()) {
1!
82
            long numConstants = ((JClassSymbol) symbol).getEnumConstants().size();
1✔
83
            // we assume there's no duplicate labels
84
            int numLabels = getBranches().sumByInt(it -> it.getLabel().getNumChildren());
1✔
85
            return numLabels == numConstants;
1✔
86
        }
87
        return false;
1✔
88
    }
89

90
    /**
91
     * Returns true if this switch block tests an expression
92
     * having an enum type.
93
     */
94
    default boolean isEnumSwitch() {
95
        JTypeDeclSymbol type = getTestedExpression().getTypeMirror().getSymbol();
1✔
96
        return type instanceof JClassSymbol && ((JClassSymbol) type).isEnum();
1!
97
    }
98

99
    /**
100
     * Returns true if this switch block tests an expression
101
     * having a sealed type or an enum type and all the possible
102
     * constants or types are covered by a switch case.
103
     * Returns false if the type of the tested expression could not
104
     * be resolved.
105
     *
106
     * @see #isExhaustiveEnumSwitch()
107
     */
108
    default boolean isExhaustive() {
109
        JTypeDeclSymbol symbol = getTestedExpression().getTypeMirror().getSymbol();
1✔
110

111
        // shortcut1 - if we have any type patterns and there is no default case,
112
        // then the compiler already ensured that the switch is exhaustive.
113
        // This assumes, we only analyze valid, compiled source code.
114
        boolean hasPatterns = getBranches().map(ASTSwitchBranch::getLabel)
1✔
115
                .any(ASTSwitchLabel::isPatternLabel);
1✔
116
        if (hasPatterns && !hasDefaultCase()) {
1✔
117
            return true;
1✔
118
        }
119

120
        if (symbol instanceof JClassSymbol) {
1!
121
            JClassSymbol classSymbol = (JClassSymbol) symbol;
1✔
122

123
            // shortcut2 - if we are dealing with a sealed type or a boolean (java 23 preview, JEP 455)
124
            // and there is no default case then the compiler already checked for exhaustiveness
125
            if (classSymbol.isSealed() || classSymbol.equals(getTypeSystem().BOOLEAN.getSymbol())) {
1✔
126
                if (!hasDefaultCase()) {
1✔
127
                    return true;
1✔
128
                }
129
            }
130

131
            if (classSymbol.isSealed()) {
1✔
132
                Set<JClassSymbol> checkedSubtypes = getBranches()
1✔
133
                        .map(ASTSwitchBranch::getLabel)
1✔
134
                        .children(ASTTypePattern.class)
1✔
135
                        .map(ASTTypePattern::getTypeNode)
1✔
136
                        .toStream()
1✔
137
                        .map(TypeNode::getTypeMirror)
1✔
138
                        .map(JTypeMirror::getSymbol)
1✔
139
                        .filter(s -> s instanceof JClassSymbol)
1✔
140
                        .map(s -> (JClassSymbol) s)
1✔
141
                        .collect(Collectors.toSet());
1✔
142

143
                Set<JClassSymbol> permittedSubtypes = new HashSet<>(classSymbol.getPermittedSubtypes());
1✔
144
                // for all the switch cases, remove the checked type itself
145
                permittedSubtypes.removeAll(checkedSubtypes);
1✔
146

147
                // if there are any remaining types left, they might be covered, if they are sealed
148
                // (there are no other possible subtypes) and all subtypes are covered
149
                // Note: This currently only checks one level. If the type hierarchy is deeper, we don't
150
                // recognize all possible permitted subtypes.
151
                for (JClassSymbol remainingType : new HashSet<>(permittedSubtypes)) {
1✔
152
                    if (remainingType.isSealed()) {
1!
153
                        Set<JClassSymbol> subtypes = new HashSet<>(remainingType.getPermittedSubtypes());
×
154
                        subtypes.removeAll(checkedSubtypes);
×
155
                        if (subtypes.isEmpty()) {
×
156
                            permittedSubtypes.remove(remainingType);
×
157
                        }
158
                    }
159
                }
1✔
160

161
                return permittedSubtypes.isEmpty();
1✔
162
            }
163
        }
164

165
        return isExhaustiveEnumSwitch();
1✔
166
    }
167

168
    @Override
169
    default Iterator<ASTSwitchBranch> iterator() {
170
        return children(ASTSwitchBranch.class).iterator();
1✔
171
    }
172

173
    /**
174
     * Returns true if this a switch which uses fallthrough branches
175
     * (old school {@code case label: break;}) and not arrow branches.
176
     * If the switch has no branches, returns false.
177
     */
178
    default boolean isFallthroughSwitch() {
179
        return getBranches().filterIs(ASTSwitchFallthroughBranch.class).nonEmpty();
1✔
180
    }
181

182
    /**
183
     * Return true if this switch accepts null. This must be explicitly
184
     * declared in a branch which matches {@link ASTSwitchLabel#isCaseNull()}.
185
     * Any switch that does not have this branch throws NullPointerException
186
     * at runtime if the scrutinee is null.
187
     * This is a feature of Java 25.
188
     * @since 7.20.0
189
     */
190
    default boolean isNullTolerant() {
191
        return getBranches().map(ASTSwitchBranch::getLabel).any(ASTSwitchLabel::isCaseNull);
1✔
192
    }
193
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc