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

pmd / pmd / 500

07 May 2026 02:03PM UTC coverage: 79.06% (+5.3%) from 73.787%
500

push

github

adangel
[java] Fix #4288: Document that CallSuperFirst and CallSuperLast are android only (#6601)

18785 of 24683 branches covered (76.11%)

Branch coverage included in aggregate %.

40922 of 50838 relevant lines covered (80.49%)

0.81 hits per line

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

95.17
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/JavaRuleUtil.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.rule.internal;
6

7
import static net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind.LONG;
8
import static net.sourceforge.pmd.util.CollectionUtil.immutableSetOf;
9

10
import java.io.InvalidObjectException;
11
import java.io.ObjectInputStream;
12
import java.io.ObjectStreamField;
13
import java.util.Set;
14

15
import org.checkerframework.checker.nullness.qual.Nullable;
16

17
import net.sourceforge.pmd.lang.java.ast.ASTArrayAccess;
18
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr;
19
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
20
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
21
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
22
import net.sourceforge.pmd.lang.java.ast.ASTBodyDeclaration;
23
import net.sourceforge.pmd.lang.java.ast.ASTClassDeclaration;
24
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
25
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
26
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
27
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
28
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
29
import net.sourceforge.pmd.lang.java.ast.ASTInitializer;
30
import net.sourceforge.pmd.lang.java.ast.ASTList;
31
import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
32
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
33
import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
34
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
35
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
36
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
37
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
38
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
39
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
40
import net.sourceforge.pmd.lang.java.ast.Annotatable;
41
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
42
import net.sourceforge.pmd.lang.java.ast.JModifier;
43
import net.sourceforge.pmd.lang.java.ast.JavaNode;
44
import net.sourceforge.pmd.lang.java.ast.ModifierOwner;
45
import net.sourceforge.pmd.lang.java.ast.ModifierOwner.Visibility;
46
import net.sourceforge.pmd.lang.java.ast.TypeNode;
47
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
48
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
49
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
50
import net.sourceforge.pmd.lang.java.types.InvocationMatcher;
51
import net.sourceforge.pmd.lang.java.types.InvocationMatcher.CompoundInvocationMatcher;
52
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
53

54
/**
55
 * Utilities shared between rules.
56
 */
57
public final class JavaRuleUtil {
58

59
    // this is a hacky way to do it, but let's see where this goes
60
    private static final CompoundInvocationMatcher KNOWN_PURE_METHODS = InvocationMatcher.parseAll(
1✔
61
        "_#toString()",
62
        "_#hashCode()",
63
        "_#equals(java.lang.Object)",
64
        "java.lang.String#_(_*)",
65
        // actually not all of them, probs only stream of some type
66
        // arg which doesn't implement Closeable...
67
        "java.util.stream.Stream#_(_*)",
68
        "java.util.stream.IntStream#_(_*)",
69
        "java.util.stream.LongStream#_(_*)",
70
        "java.util.stream.DoubleStream#_(_*)",
71
        "java.util.Collection#contains(_)",
72
        "java.util.Collection#size()",
73
        "java.util.List#get(int)",
74
        "java.util.Map#get(_)",
75
        "java.lang.Iterable#iterator()",
76
        "java.lang.Comparable#compareTo(_)",
77
        "java.math.BigDecimal#_(_*)",
78
        "java.math.BigInteger#_(_*)",
79
        "java.time.temporal.Temporal#_(_*)",
80
        "java.time.Duration#_(_*)",
81
        "java.time.Period#_(_*)"
82
    );
83

84
    private static final CompoundInvocationMatcher KNOWN_SIDE_EFFECT_METHODS =
1✔
85
            InvocationMatcher.parseAll(
1✔
86
                "_#getAndIncrement()",
87
                "_#getAndDecrement()",
88
                "_#getNextEntry()"
89
            );
90

91
    public static final Set<String> LOMBOK_ANNOTATIONS = immutableSetOf(
1✔
92
        "lombok.Data",
93
        "lombok.Getter",
94
        "lombok.Setter",
95
        "lombok.Value",
96
        "lombok.RequiredArgsConstructor",
97
        "lombok.AllArgsConstructor",
98
        "lombok.NoArgsConstructor",
99
        "lombok.Builder",
100
        "lombok.EqualsAndHashCode",
101
        "lombok.experimental.Delegate"
102
    );
103

104
    private JavaRuleUtil() {
105
        // utility class
106
    }
107

108

109
    /**
110
     * Return true if the given expression is enclosed in a zero check.
111
     * The expression must evaluate to a natural number (ie >= 0), so that
112
     * {@code e < 1} actually means {@code e == 0}.
113
     *
114
     * @param e Expression
115
     */
116
    public static boolean isZeroChecked(ASTExpression e) {
117
        JavaNode parent = e.getParent();
1✔
118
        if (parent instanceof ASTInfixExpression) {
1✔
119
            BinaryOp op = ((ASTInfixExpression) parent).getOperator();
1✔
120
            int checkLiteralAtIdx = 1 - e.getIndexInParent();
1✔
121
            JavaNode comparand = parent.getChild(checkLiteralAtIdx);
1✔
122
            int expectedValue;
123
            if (op == BinaryOp.NE || op == BinaryOp.EQ) {
1✔
124
                // e == 0, e != 0, symmetric
125
                expectedValue = 0;
1✔
126
            } else if (op == BinaryOp.LT || op == BinaryOp.GE) {
1✔
127
                // e < 1
128
                // 0 < e
129
                // e >= 1     (e != 0)
130
                // 1 >= e     (e == 0 || e == 1)
131
                // 0 >= e     (e == 0)
132
                // e >= 0     (true)
133
                expectedValue = checkLiteralAtIdx;
1✔
134
            } else if (op == BinaryOp.GT || op == BinaryOp.LE) {
1✔
135
                // 1 > e
136
                // e > 0
137

138
                // 1 <= e     (e != 0)
139
                // e <= 1     (e == 0 || e == 1)
140
                // e <= 0     (e == 0)
141
                // 0 <= e     (true)
142
                expectedValue = 1 - checkLiteralAtIdx;
1✔
143
            } else {
144
                return false;
1✔
145
            }
146

147
            return JavaAstUtils.isLiteralInt(comparand, expectedValue);
1✔
148
        }
149
        return false;
1✔
150
    }
151

152

153
    /**
154
     * Returns true if the expression is a stringbuilder (or stringbuffer)
155
     * append call, or a constructor call for one of these classes.
156
     *
157
     * <p>If it is a constructor call, returns false if this is a call to
158
     * the constructor with a capacity parameter.
159
     */
160
    public static boolean isStringBuilderCtorOrAppend(@Nullable ASTExpression e) {
161
        if (e instanceof ASTMethodCall) {
1✔
162
            ASTMethodCall call = (ASTMethodCall) e;
1✔
163
            if ("append".equals(call.getMethodName())) {
1✔
164
                ASTExpression qual = ((ASTMethodCall) e).getQualifier();
1✔
165
                return qual != null && isStringBufferOrBuilder(qual);
1✔
166
            }
167
        } else if (e instanceof ASTConstructorCall) {
1!
168
            return isStringBufferOrBuilder(((ASTConstructorCall) e).getTypeNode());
1✔
169
        }
170
        return false;
1✔
171
    }
172

173
    private static boolean isStringBufferOrBuilder(TypeNode node) {
174
        return TypeTestUtil.isExactlyA(StringBuilder.class, node)
1✔
175
            || TypeTestUtil.isExactlyA(StringBuffer.class, node);
1✔
176
    }
177

178
    /**
179
     * Returns true if the node is a utility class, according to this
180
     * custom definition.
181
     */
182
    public static boolean isUtilityClass(ASTTypeDeclaration node) {
183
        if (!node.isRegularClass()) {
1✔
184
            return false;
1✔
185
        }
186

187
        ASTClassDeclaration classNode = (ASTClassDeclaration) node;
1✔
188

189
        // A class with a superclass or interfaces should not be considered
190
        if (classNode.getSuperClassTypeNode() != null
1✔
191
            || !classNode.getSuperInterfaceTypeNodes().isEmpty()) {
1✔
192
            return false;
1✔
193
        }
194

195
        // A class without declarations shouldn't be reported
196
        boolean hasAny = false;
1✔
197

198
        for (ASTBodyDeclaration declNode : classNode.getDeclarations()) {
1✔
199
            if (declNode instanceof ASTFieldDeclaration
1✔
200
                || declNode instanceof ASTMethodDeclaration) {
201

202
                hasAny = isNonPrivate(declNode) && !JavaAstUtils.isMainMethod(declNode);
1✔
203
                if (!((ModifierOwner) declNode).hasModifiers(JModifier.STATIC)) {
1✔
204
                    return false;
1✔
205
                }
206

207
            } else if (declNode instanceof ASTInitializer) {
1✔
208
                if (!((ASTInitializer) declNode).isStatic()) {
1✔
209
                    return false;
1✔
210
                }
211
            }
212
        }
1✔
213

214
        return hasAny;
1✔
215
    }
216

217
    private static boolean isNonPrivate(ASTBodyDeclaration decl) {
218
        return ((ModifierOwner) decl).getVisibility() != Visibility.V_PRIVATE;
1✔
219
    }
220

221
    /**
222
     * Whether the name may be ignored by unused rules like UnusedAssignment.
223
     */
224
    public static boolean isExplicitUnusedVarName(String name) {
225
        return name.startsWith("ignored")
1✔
226
            || name.startsWith("unused")
1✔
227
            // before java 9 it's ok, after that, "_" is a reserved keyword
228
            // with Java 21 Preview (JEP 443), "_" means explicitly unused
229
            || "_".equals(name);
1✔
230
    }
231

232
    /**
233
     * Returns true if the string has the given word as a strict prefix.
234
     * There needs to be a camelcase word boundary after the prefix.
235
     *
236
     * <code>
237
     * startsWithCamelCaseWord("getter", "get") == false
238
     * startsWithCamelCaseWord("get", "get")    == false
239
     * startsWithCamelCaseWord("getX", "get")   == true
240
     * </code>
241
     *
242
     * @param camelCaseString A string
243
     * @param prefixWord      A prefix
244
     */
245
    public static boolean startsWithCamelCaseWord(String camelCaseString, String prefixWord) {
246
        return camelCaseString.startsWith(prefixWord)
1✔
247
            && camelCaseString.length() > prefixWord.length()
1✔
248
            && Character.isUpperCase(camelCaseString.charAt(prefixWord.length()));
1✔
249
    }
250

251

252
    /**
253
     * Returns true if the string has the given word as a word, not at the start.
254
     * There needs to be a camelcase word boundary after the prefix.
255
     *
256
     * <code>
257
     * containsCamelCaseWord("isABoolean", "Bool") == false
258
     * containsCamelCaseWord("isABoolean", "A")    == true
259
     * containsCamelCaseWord("isABoolean", "is")   == error (not capitalized)
260
     * </code>
261
     *
262
     * @param camelCaseString A string
263
     * @param capitalizedWord A word, non-empty, capitalized
264
     *
265
     * @throws AssertionError If the word is empty or not capitalized
266
     */
267
    public static boolean containsCamelCaseWord(String camelCaseString, String capitalizedWord) {
268
        assert capitalizedWord.length() > 0 && Character.isUpperCase(capitalizedWord.charAt(0))
1✔
269
            : "Not a capitalized string \"" + capitalizedWord + "\"";
270

271
        int index = camelCaseString.indexOf(capitalizedWord);
1✔
272
        if (index >= 0 && camelCaseString.length() > index + capitalizedWord.length()) {
1✔
273
            return Character.isUpperCase(camelCaseString.charAt(index + capitalizedWord.length()));
1✔
274
        }
275
        return index >= 0 && camelCaseString.length() == index + capitalizedWord.length();
1!
276
    }
277

278
    public static boolean isGetterOrSetterCall(ASTMethodCall call) {
279
        return isGetterCall(call) || isSetterCall(call);
1✔
280
    }
281

282
    private static boolean isSetterCall(ASTMethodCall call) {
283
        return call.getArguments().size() > 0 && startsWithCamelCaseWord(call.getMethodName(), "set");
1✔
284
    }
285

286
    public static boolean isGetterCall(ASTMethodCall call) {
287
        return call.getArguments().isEmpty()
1✔
288
            && (startsWithCamelCaseWord(call.getMethodName(), "get")
1✔
289
            || startsWithCamelCaseWord(call.getMethodName(), "is"))
1✔
290
            && !call.getMethodType().getReturnType().isVoid();
1✔
291
    }
292

293
    public static boolean isGetterOrSetter(ASTMethodDeclaration node) {
294
        return isGetter(node) || isSetter(node);
1✔
295
    }
296

297
    /** Attempts to determine if the method is a getter. */
298
    private static boolean isGetter(ASTMethodDeclaration node) {
299

300
        if (node.getArity() != 0 || node.isVoid()) {
1✔
301
            return false;
1✔
302
        }
303

304
        ASTTypeDeclaration enclosing = node.getEnclosingType();
1✔
305
        if (startsWithCamelCaseWord(node.getName(), "get")) {
1✔
306
            return JavaAstUtils.hasField(enclosing, node.getName().substring(3));
1✔
307
        } else if (startsWithCamelCaseWord(node.getName(), "is")
1✔
308
                && TypeTestUtil.isA(boolean.class, node.getResultTypeNode())) {
1✔
309
            return JavaAstUtils.hasField(enclosing, node.getName().substring(2));
1✔
310
        }
311

312
        return JavaAstUtils.hasField(enclosing, node.getName());
1✔
313
    }
314

315
    /** Attempts to determine if the method is a setter. */
316
    private static boolean isSetter(ASTMethodDeclaration node) {
317

318
        if (node.getArity() != 1 || !node.isVoid()) {
1✔
319
            return false;
1✔
320
        }
321

322
        ASTTypeDeclaration enclosing = node.getEnclosingType();
1✔
323

324
        if (startsWithCamelCaseWord(node.getName(), "set")) {
1✔
325
            return JavaAstUtils.hasField(enclosing, node.getName().substring(3));
1✔
326
        }
327

328
        return JavaAstUtils.hasField(enclosing, node.getName());
1✔
329
    }
330

331
    // TODO at least UnusedPrivateMethod has some serialization-related logic.
332

333
    /**
334
     * Whether some variable declared by the given node is a serialPersistentFields
335
     * (serialization-specific field).
336
     */
337
    public static boolean isSerialPersistentFields(final ASTFieldDeclaration field) {
338
        return field.hasModifiers(JModifier.FINAL, JModifier.STATIC, JModifier.PRIVATE)
1✔
339
            && field.getVarIds().any(it -> "serialPersistentFields".equals(it.getName()) && TypeTestUtil.isA(ObjectStreamField[].class, it));
1✔
340
    }
341

342
    /**
343
     * Whether some variable declared by the given node is a serialVersionUID
344
     * (serialization-specific field).
345
     */
346
    public static boolean isSerialVersionUID(ASTFieldDeclaration field) {
347
        return field.hasModifiers(JModifier.FINAL, JModifier.STATIC)
1✔
348
            && field.getVarIds().any(it -> "serialVersionUID".equals(it.getName()) && it.getTypeMirror().isPrimitive(LONG));
1✔
349
    }
350

351
    /**
352
     * True if the method is a {@code readObject} method defined for serialization.
353
     */
354
    public static boolean isSerializationReadObject(ASTMethodDeclaration node) {
355
        return node.getVisibility() == Visibility.V_PRIVATE
1✔
356
            && "readObject".equals(node.getName())
1✔
357
            && JavaAstUtils.hasExceptionList(node, InvalidObjectException.class)
1✔
358
            && JavaAstUtils.hasParameters(node, ObjectInputStream.class);
1✔
359
    }
360

361

362
    /**
363
     * Whether the node or one of its descendants is an expression with
364
     * side effects. Conservatively, any method call is a potential side-effect,
365
     * as well as assignments to fields or array elements. We could relax
366
     * this assumption with (much) more data-flow logic, including a memory model.
367
     *
368
     * <p>By default assignments to locals are not counted as side-effects,
369
     * unless the lhs is in the given set of symbols.
370
     *
371
     * @param node             A node
372
     * @param localVarsToTrack Local variables to track
373
     */
374
    public static boolean hasSideEffect(@Nullable JavaNode node, Set<? extends JVariableSymbol> localVarsToTrack) {
375
        return node != null && node.descendantsOrSelf()
1✔
376
                                   .filterIs(ASTExpression.class)
1✔
377
                                   .any(e -> hasSideEffectNonRecursive(e, localVarsToTrack));
1✔
378
    }
379

380
    /**
381
     * Returns true if the expression has side effects we don't track.
382
     * Does not recurse into sub-expressions.
383
     */
384
    private static boolean hasSideEffectNonRecursive(ASTExpression e, Set<? extends JVariableSymbol> localVarsToTrack) {
385
        if (e instanceof ASTAssignmentExpression) {
1✔
386
            ASTAssignableExpr lhs = ((ASTAssignmentExpression) e).getLeftOperand();
1✔
387
            return isNonLocalLhs(lhs) || JavaAstUtils.isReferenceToVar(lhs, localVarsToTrack);
1✔
388
        } else if (e instanceof ASTUnaryExpression) {
1✔
389
            ASTUnaryExpression unary = (ASTUnaryExpression) e;
1✔
390
            ASTExpression lhs = unary.getOperand();
1✔
391
            return !unary.getOperator().isPure()
1!
392
                && (isNonLocalLhs(lhs) || JavaAstUtils.isReferenceToVar(lhs, localVarsToTrack));
1!
393
        }
394

395
        // when there are throw statements,
396
        // then this side effect can never be observed in containing code,
397
        // because control flow jumps out of the method
398
        return e.ancestors(ASTThrowStatement.class).isEmpty()
1✔
399
                && (e instanceof ASTMethodCall && !isPure((ASTMethodCall) e)
1✔
400
                        || e instanceof ASTConstructorCall);
401
    }
402

403
    private static boolean isNonLocalLhs(ASTExpression lhs) {
404
        return lhs instanceof ASTArrayAccess || !JavaAstUtils.isReferenceToLocal(lhs);
1!
405
    }
406

407
    /**
408
     * Whether the invocation has no side-effects. Very conservative.
409
     */
410
    private static boolean isPure(ASTMethodCall call) {
411
        return isGetterCall(call) || KNOWN_PURE_METHODS.anyMatch(call);
1✔
412
    }
413

414
    /**
415
     * Whether the invocation has no side effects. Even more conservative than {@code isPure}.
416
     * Only checks methods in java.* packages because frameworks may define getter-like methods
417
     * with side effects (such as isEqualTo matcher in AssertJ).
418
     */
419
    public static boolean isKnownPure(ASTMethodCall call) {
420
        return isGetterCall(call)
1✔
421
                    && isPureGetter(call)
1✔
422
            || KNOWN_PURE_METHODS.anyMatch(call) && !call.getMethodType().getReturnType().isVoid();
1✔
423
    }
424

425
    private static boolean isPureGetter(ASTMethodCall call) {
426
        JTypeDeclSymbol symbol = call.getMethodType().getDeclaringType().getSymbol();
1✔
427
        if (symbol == null) {
1!
428
            return false;
×
429
        }
430
        String pkg = symbol.getPackageName();
1✔
431
        return pkg.startsWith("java.")
1✔
432
            && !pkg.startsWith("java.nio") && !pkg.startsWith("java.net")
1!
433
            && !KNOWN_SIDE_EFFECT_METHODS.anyMatch(call);
1✔
434
    }
435

436
    public static @Nullable ASTVariableId getReferencedNode(ASTNamedReferenceExpr expr) {
437
        JVariableSymbol referencedSym = expr.getReferencedSym();
×
438
        return referencedSym == null ? null : referencedSym.tryGetNode();
×
439
    }
440

441
    /**
442
     * Checks whether the given node is annotated with any lombok annotation.
443
     * The node should be annotateable.
444
     *
445
     * @param node
446
     *            the Annotatable node to check
447
     * @return <code>true</code> if a lombok annotation has been found
448
     */
449
    public static boolean hasLombokAnnotation(Annotatable node) {
450
        return LOMBOK_ANNOTATIONS.stream().anyMatch(node::isAnnotationPresent);
×
451
    }
452

453
    /**
454
     * Returns true if the expression is a null check on the given variable.
455
     */
456
    public static boolean isNullCheck(ASTExpression expr, JVariableSymbol var) {
457
        return isNullCheck(expr, StablePathMatcher.matching(var));
1✔
458
    }
459

460
    public static boolean isNullCheck(ASTExpression expr, StablePathMatcher matcher) {
461
        if (expr instanceof ASTInfixExpression) {
1✔
462
            ASTInfixExpression condition = (ASTInfixExpression) expr;
1✔
463
            if (condition.getOperator().hasSamePrecedenceAs(BinaryOp.EQ)) {
1✔
464
                ASTNullLiteral nullLit = condition.firstChild(ASTNullLiteral.class);
1✔
465
                if (nullLit != null) {
1✔
466
                    return matcher.matches(JavaAstUtils.getOtherOperandIfInInfixExpr(nullLit));
1✔
467
                }
468
            }
469
        }
470
        return false;
1✔
471
    }
472

473
    /**
474
     * Returns true if the expr is in a null check (its parent is a null check).
475
     */
476
    public static boolean isNullChecked(ASTExpression expr) {
477
        if (expr.getParent() instanceof ASTInfixExpression) {
1✔
478
            ASTInfixExpression infx = (ASTInfixExpression) expr.getParent();
1✔
479
            if (infx.getOperator().hasSamePrecedenceAs(BinaryOp.EQ)) {
1✔
480
                return JavaAstUtils.getOtherOperandIfInInfixExpr(expr) instanceof ASTNullLiteral;
1✔
481
            }
482
        }
483
        return false;
1✔
484
    }
485

486
    /**
487
     * Determine if the node argument has the typical form of a "guard if", that is
488
     * an if statement with no else block, and the only thing the then block does is either
489
     * return or throw an expression.
490
     *
491
     * Does NOT check if the node is at the beginning of a function!
492
     */
493
    public static boolean isGuardIf(JavaNode node) {
494
        if (!(node instanceof ASTIfStatement)) {
1✔
495
            return false;
1✔
496
        }
497
        ASTIfStatement ifStatement = (ASTIfStatement) node;
1✔
498

499
        if (ifStatement.getElseBranch() != null) {
1✔
500
            return false;
1✔
501
        }
502

503
        ASTStatement thenBranch = ifStatement.getThenBranch();
1✔
504
        ASTStatement onlyStatementInThenBranch;
505
        if (thenBranch instanceof ASTBlock) {
1✔
506
            onlyStatementInThenBranch = ASTList.singleOrNull((ASTBlock) thenBranch);
1✔
507
            if (onlyStatementInThenBranch == null) {
1✔
508
                return false;
1✔
509
            }
510
        } else {
511
            onlyStatementInThenBranch = thenBranch;
1✔
512
        }
513

514
        return onlyStatementInThenBranch instanceof ASTReturnStatement
1✔
515
                || onlyStatementInThenBranch instanceof ASTThrowStatement;
516
    }
517

518
}
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