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

pmd / pmd / 489

24 Apr 2026 09:16AM UTC coverage: 79.012% (-0.02%) from 79.035%
489

push

github

adangel
[java] Fix #4272: False positive in UnitTestShouldIncludeAssert when using assertion in lambda (#6556)

18715 of 24609 branches covered (76.05%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

17 existing lines in 4 files now uncovered.

40799 of 50714 relevant lines covered (80.45%)

0.81 hits per line

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

78.98
/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/internal/Helper.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.apex.rule.internal;
6

7
import java.util.Arrays;
8
import java.util.List;
9
import java.util.Locale;
10

11
import net.sourceforge.pmd.lang.apex.ast.ASTDmlDeleteStatement;
12
import net.sourceforge.pmd.lang.apex.ast.ASTDmlInsertStatement;
13
import net.sourceforge.pmd.lang.apex.ast.ASTDmlMergeStatement;
14
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUndeleteStatement;
15
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpdateStatement;
16
import net.sourceforge.pmd.lang.apex.ast.ASTDmlUpsertStatement;
17
import net.sourceforge.pmd.lang.apex.ast.ASTField;
18
import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclaration;
19
import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression;
20
import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode;
21
import net.sourceforge.pmd.lang.apex.ast.ASTNewKeyValueObjectExpression;
22
import net.sourceforge.pmd.lang.apex.ast.ASTNewObjectExpression;
23
import net.sourceforge.pmd.lang.apex.ast.ASTParameter;
24
import net.sourceforge.pmd.lang.apex.ast.ASTReferenceExpression;
25
import net.sourceforge.pmd.lang.apex.ast.ASTSoqlExpression;
26
import net.sourceforge.pmd.lang.apex.ast.ASTSoslExpression;
27
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
28
import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration;
29
import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression;
30
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
31

32
/**
33
 * Helper methods
34
 *
35
 * @author sergey.gorbaty
36
 * @author dschach
37
 *
38
 */
39
public final class Helper {
40
    public static final String ANY_METHOD = "*";
41
    private static final String DATABASE_CLASS_NAME = "Database";
42

43
    private Helper() {
×
44
        throw new AssertionError("Can't instantiate helper classes");
×
45
    }
46

47
    /**
48
     * Check if this node is a test method or a test class.
49
     * It used to do a check if the class name ended in "Test", but that is not specific.
50
     * @author sergey.gorbaty
51
     * @author dschach
52
     * @since 7.24.0 No longer check class name. Only isTest() in apex parser is used.
53
     * 
54
     * @param node The node to check
55
     * @return `true` if test class or method
56
     */
57
    public static boolean isTestMethodOrClass(final ApexNode<?> node) {
58
        for (final ASTModifierNode m : node.children(ASTModifierNode.class)) {
1✔
59
            if (m.isTest()) {
1✔
60
                return true;
1✔
61
            }
62
        }
1✔
63
        return false;
1✔
64
    }
65

66
    public static boolean foundAnySOQLorSOSL(final ApexNode<?> node) {
67
        return node.descendants(ASTSoqlExpression.class).nonEmpty()
×
68
                || node.descendants(ASTSoslExpression.class).nonEmpty();
×
69
    }
70

71
    /**
72
     * Finds DML operations in a given node descendants' path
73
     *
74
     * @param node
75
     *
76
     * @return true if found DML operations in node descendants
77
     */
78
    public static boolean foundAnyDML(final ApexNode<?> node) {
79

80
        final List<ASTDmlUpsertStatement> dmlUpsertStatement = node.descendants(ASTDmlUpsertStatement.class).toList();
1✔
81
        final List<ASTDmlUpdateStatement> dmlUpdateStatement = node.descendants(ASTDmlUpdateStatement.class).toList();
1✔
82
        final List<ASTDmlUndeleteStatement> dmlUndeleteStatement = node.descendants(ASTDmlUndeleteStatement.class).toList();
1✔
83
        final List<ASTDmlMergeStatement> dmlMergeStatement = node.descendants(ASTDmlMergeStatement.class).toList();
1✔
84
        final List<ASTDmlInsertStatement> dmlInsertStatement = node.descendants(ASTDmlInsertStatement.class).toList();
1✔
85
        final List<ASTDmlDeleteStatement> dmlDeleteStatement = node.descendants(ASTDmlDeleteStatement.class).toList();
1✔
86

87
        return !dmlUpsertStatement.isEmpty() || !dmlUpdateStatement.isEmpty() || !dmlUndeleteStatement.isEmpty()
1!
88
                || !dmlMergeStatement.isEmpty() || !dmlInsertStatement.isEmpty() || !dmlDeleteStatement.isEmpty();
1!
89
    }
90

91
    public static boolean isMethodName(final ASTMethodCallExpression methodNode, final String className,
92
            final String methodName) {
93
        final ASTReferenceExpression reference = methodNode.firstChild(ASTReferenceExpression.class);
1✔
94

95
        return reference != null && reference.getNames().size() == 1
1✔
96
                && reference.getNames().get(0).equalsIgnoreCase(className)
1✔
97
                && (ANY_METHOD.equals(methodName) || isMethodName(methodNode, methodName));
1✔
98
    }
99

100
    public static boolean isMethodName(final ASTMethodCallExpression m, final String methodName) {
101
        return m.getMethodName().equalsIgnoreCase(methodName);
1✔
102
    }
103

104
    public static boolean isMethodCallChain(final ASTMethodCallExpression methodNode, final String... methodNames) {
105
        String methodName = methodNames[methodNames.length - 1];
1✔
106
        if (Helper.isMethodName(methodNode, methodName)) {
1✔
107
            final ASTReferenceExpression reference = methodNode.firstChild(ASTReferenceExpression.class);
1✔
108
            if (reference != null) {
1!
109
                final ASTMethodCallExpression nestedMethod = reference
1✔
110
                        .firstChild(ASTMethodCallExpression.class);
1✔
111
                if (nestedMethod != null) {
1✔
112
                    String[] newMethodNames = Arrays.copyOf(methodNames, methodNames.length - 1);
1✔
113
                    return isMethodCallChain(nestedMethod, newMethodNames);
1✔
114
                } else {
115
                    String[] newClassName = Arrays.copyOf(methodNames, methodNames.length - 1);
1✔
116
                    if (newClassName.length == 1) {
1!
117
                        return Helper.isMethodName(methodNode, newClassName[0], methodName);
1✔
118
                    }
119
                }
120
            }
121
        }
122

123
        return false;
1✔
124
    }
125

126
    public static String getFQVariableName(final ASTVariableExpression variable) {
127
        final ASTReferenceExpression ref = variable.firstChild(ASTReferenceExpression.class);
1✔
128
        String objectName = "";
1✔
129
        if (ref != null && ref.getNames().size() == 1) {
1!
130
            objectName = ref.getNames().get(0) + ".";
1✔
131
        }
132

133
        StringBuilder sb = new StringBuilder().append(variable.getDefiningType()).append(":").append(objectName)
1✔
134
                .append(variable.getImage());
1✔
135
        return sb.toString();
1✔
136
    }
137

138
    public static String getFQVariableName(final ASTVariableDeclaration variable) {
139
        StringBuilder sb = new StringBuilder().append(variable.getDefiningType()).append(":")
1✔
140
                .append(variable.getImage());
1✔
141
        return sb.toString();
1✔
142
    }
143

144
    public static String getFQVariableName(final ASTField variable) {
145
        StringBuilder sb = new StringBuilder()
1✔
146
                .append(variable.getDefiningType()).append(":")
1✔
147
                .append(variable.getName());
1✔
148
        return sb.toString();
1✔
149
    }
150

151
    static String getVariableType(final ASTField variable) {
152
        StringBuilder sb = new StringBuilder().append(variable.getDefiningType()).append(":")
×
153
                .append(variable.getName());
×
154
        return sb.toString();
×
155
    }
156

157
    public static String getFQVariableName(final ASTFieldDeclaration variable) {
158
        StringBuilder sb = new StringBuilder()
1✔
159
                .append(variable.getDefiningType()).append(":")
1✔
160
                .append(variable.getImage());
1✔
161
        return sb.toString();
1✔
162
    }
163

164
    public static String getFQVariableName(final ASTNewKeyValueObjectExpression variable) {
165
        StringBuilder sb = new StringBuilder()
1✔
166
                .append(variable.getDefiningType()).append(":")
1✔
167
                .append(variable.getType());
1✔
168
        return sb.toString();
1✔
169
    }
170

171
    public static String getFQVariableName(final ASTNewObjectExpression variable) {
172
        StringBuilder sb = new StringBuilder()
1✔
173
                .append(variable.getDefiningType()).append(":")
1✔
174
                .append(variable.getType());
1✔
175
        return sb.toString();
1✔
176
    }
177

178
    public static boolean isSystemLevelClass(ASTUserClass node) {
179
        List<String> interfaces = node.getInterfaceNames();
1✔
180
        return interfaces.stream().anyMatch(Helper::isAllowed);
1✔
181
    }
182

183
    private static boolean isAllowed(String identifier) {
184
        switch (identifier.toLowerCase(Locale.ROOT)) {
1!
185
        case "queueable":
186
        case "database.batchable":
187
        case "installhandler":
188
            return true;
1✔
189
        default:
190
            break;
191
        }
192
        return false;
×
193
    }
194

195
    public static String getFQVariableName(ASTParameter p) {
196
        StringBuilder sb = new StringBuilder();
1✔
197
        sb.append(p.getDefiningType()).append(":").append(p.getImage());
1✔
198
        return sb.toString();
1✔
199
    }
200

201
    /**
202
     * @return true if {@code node} is an invocation of a {@code Database} method.
203
     */
204
    public static boolean isAnyDatabaseMethodCall(ASTMethodCallExpression node) {
205
        return isMethodName(node, DATABASE_CLASS_NAME, ANY_METHOD);
1✔
206
    }
207

208
    /**
209
     * Extracts the key type from a Map type string, e.g. {@code "Map<IKey, String>"} returns {@code "IKey"}.
210
     * Handles nested generics e.g. {@code "Map<List<A>, B>"} returns {@code "List<A>"}.
211
     *
212
     * @param typeName full type string (e.g. from {@link net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration#getType()})
213
     * @return the key type, or null if the type is not Map&lt;K,V&gt; or malformed
214
     */
215
    public static String getMapKeyType(String typeName) {
216
        if (typeName == null) {
1!
UNCOV
217
            return null;
×
218
        }
219
        String t = typeName.trim();
1✔
220
        if (!t.startsWith("Map<")) {
1✔
221
            return null;
1✔
222
        }
223
        int start = t.indexOf('<');
1✔
224
        if (start < 0) {
1!
UNCOV
225
            return null;
×
226
        }
227
        int depth = 1;
1✔
228
        int i = start + 1;
1✔
229
        int keyStart = i;
1✔
230
        while (i < t.length() && depth > 0) {
1!
231
            char c = t.charAt(i);
1✔
232
            if (c == '<') {
1!
UNCOV
233
                depth++;
×
234
            } else if (c == '>') {
1!
UNCOV
235
                depth--;
×
236
            } else if (c == ',' && depth == 1) {
1!
237
                return t.substring(keyStart, i).trim();
1✔
238
            }
239
            i++;
1✔
240
        }
1✔
UNCOV
241
        return null;
×
242
    }
243
}
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