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

pmd / pmd / 4380

30 Jan 2025 09:37AM UTC coverage: 77.689% (+0.005%) from 77.684%
4380

push

github

adangel
[java] Fix tests

17353 of 23278 branches covered (74.55%)

Branch coverage included in aggregate %.

38134 of 48144 relevant lines covered (79.21%)

0.8 hits per line

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

86.67
/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
    /**
49
     * Returns a stream of all branches of this switch.
50
     */
51
    default NodeStream<ASTSwitchBranch> getBranches() {
52
        return children(ASTSwitchBranch.class);
1✔
53
    }
54

55

56
    /**
57
     * Gets the expression tested by this switch.
58
     * This is the expression between the parentheses.
59
     */
60
    default ASTExpression getTestedExpression() {
61
        return (ASTExpression) getChild(0);
1✔
62
    }
63

64

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

82
    /**
83
     * Returns true if this switch block tests an expression
84
     * having an enum type.
85
     */
86
    default boolean isEnumSwitch() {
87
        JTypeDeclSymbol type = getTestedExpression().getTypeMirror().getSymbol();
1✔
88
        return type instanceof JClassSymbol && ((JClassSymbol) type).isEnum();
1!
89
    }
90

91
    /**
92
     * Returns true if this switch block tests an expression
93
     * having a sealed type or an enum type and all the possible
94
     * constants or types are covered by a switch case.
95
     * Returns false if the type of the tested expression could not
96
     * be resolved.
97
     *
98
     * @see #isExhaustiveEnumSwitch()
99
     */
100
    default boolean isExhaustive() {
101
        JTypeDeclSymbol symbol = getTestedExpression().getTypeMirror().getSymbol();
1✔
102

103
        // shortcut1 - if we have any type patterns and there is no default case,
104
        // then the compiler already ensured that the switch is exhaustive.
105
        // This assumes, we only analyze valid, compiled source code.
106
        boolean hasPatterns = getBranches().map(ASTSwitchBranch::getLabel)
1✔
107
                .any(ASTSwitchLabel::isPatternLabel);
1✔
108
        if (hasPatterns && !hasDefaultCase()) {
1✔
109
            return true;
1✔
110
        }
111

112
        if (symbol instanceof JClassSymbol) {
1!
113
            JClassSymbol classSymbol = (JClassSymbol) symbol;
1✔
114

115
            // shortcut2 - if we are dealing with a sealed type or a boolean (java 23 preview, JEP 455)
116
            // and there is no default case then the compiler already checked for exhaustiveness
117
            if (classSymbol.isSealed() || classSymbol.equals(getTypeSystem().BOOLEAN.getSymbol())) {
1✔
118
                if (!hasDefaultCase()) {
1✔
119
                    return true;
1✔
120
                }
121
            }
122

123
            if (classSymbol.isSealed()) {
1✔
124
                Set<JClassSymbol> checkedSubtypes = getBranches()
1✔
125
                        .map(ASTSwitchBranch::getLabel)
1✔
126
                        .children(ASTTypePattern.class)
1✔
127
                        .map(ASTTypePattern::getTypeNode)
1✔
128
                        .toStream()
1✔
129
                        .map(TypeNode::getTypeMirror)
1✔
130
                        .map(JTypeMirror::getSymbol)
1✔
131
                        .filter(s -> s instanceof JClassSymbol)
1✔
132
                        .map(s -> (JClassSymbol) s)
1✔
133
                        .collect(Collectors.toSet());
1✔
134

135
                Set<JClassSymbol> permittedSubtypes = new HashSet<>(classSymbol.getPermittedSubtypes());
1✔
136
                // for all the switch cases, remove the checked type itself
137
                permittedSubtypes.removeAll(checkedSubtypes);
1✔
138

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

153
                return permittedSubtypes.isEmpty();
1✔
154
            }
155
        }
156

157
        return isExhaustiveEnumSwitch();
1✔
158
    }
159

160
    @Override
161
    default Iterator<ASTSwitchBranch> iterator() {
162
        return children(ASTSwitchBranch.class).iterator();
1✔
163
    }
164

165
    /**
166
     * Returns true if this a switch which uses fallthrough branches
167
     * (old school {@code case label: break;}) and not arrow branches.
168
     * If the switch has no branches, returns false.
169
     */
170
    default boolean isFallthroughSwitch() {
171
        return getBranches().filterIs(ASTSwitchFallthroughBranch.class).nonEmpty();
1✔
172
    }
173

174
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc