• 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

86.02
/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileAnalysis.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.multifile;
6

7
import java.nio.file.Files;
8
import java.nio.file.Path;
9
import java.nio.file.Paths;
10
import java.util.ArrayList;
11
import java.util.Arrays;
12
import java.util.Collections;
13
import java.util.List;
14
import java.util.Optional;
15

16
import org.checkerframework.checker.nullness.qual.Nullable;
17
import org.slf4j.Logger;
18
import org.slf4j.LoggerFactory;
19

20
import net.sourceforge.pmd.lang.apex.ApexLanguageProcessor;
21
import net.sourceforge.pmd.lang.apex.ApexLanguageProperties;
22

23
import com.nawforce.apexlink.api.Org;
24
import com.nawforce.apexlink.api.Package;
25
import com.nawforce.apexlink.api.TypeSummary;
26
import com.nawforce.pkgforce.diagnostics.LoggerOps;
27
import com.nawforce.pkgforce.names.TypeIdentifier;
28
import io.github.apexdevtools.api.Issue;
29

30
/**
31
 * Stores multi-file analysis data. The 'Org' here is the primary ApexLink structure for maintaining information
32
 * about the Salesforce metadata. We load 'Packages' into it to perform analysis. Once constructed you
33
 * can get 'Issue' information from it on what was found. The 'Org' holds mutable state for IDE use that can get quite
34
 * large (a few hundred MB on very large projects). An alternative way to use this would be to cache the
35
 * issues after packages are loaded and throw away the 'Org'. That would be a better model if all you wanted was the
36
 * issues but more complex rules will need the ability to traverse the internal graph of the 'Org'.
37
 *
38
 * <p>Note: This is used by {@link net.sourceforge.pmd.lang.apex.rule.design.UnusedMethodRule}.
39
 *
40
 * @author Kevin Jones
41
 */
42
public final class ApexMultifileAnalysis {
43

44
    // test only
45
    static final Logger LOG = LoggerFactory.getLogger(ApexMultifileAnalysis.class);
1✔
46

47
    private boolean warnedAboutMissingOrg = false;
1✔
48

49
    // Create a new org for each analysis
50
    // Null if failed.
51
    private final @Nullable Org org;
52

53
    // Lazily computed flat list of all TypeSummary objects in the org (including nested types).
54
    private List<TypeSummary> allTypeSummaries = null;
1✔
55

56
    static {
57
        // Setup logging
58
        LoggerOps.setLogger(new AnalysisLogger());
1✔
59
        // TODO: Provide means to control logging
60
        LoggerOps.setLoggingLevel(LoggerOps.NO_LOGGING());
1✔
61
    }
1✔
62

63

64
    ApexMultifileAnalysis(ApexLanguageProperties properties) {
1✔
65
        Optional<String> rootDir = properties.getProperty(ApexLanguageProperties.MULTIFILE_DIRECTORY);
1✔
66
        LOG.debug("MultiFile Analysis created for {}", rootDir);
1✔
67

68
        Org org = null;
1✔
69
        try {
70
            // Load the package into the org, this can take some time!
71
            if (rootDir.isPresent() && !rootDir.get().isEmpty()) {
1!
72
                Path projectPath = Paths.get(rootDir.get());
1✔
73
                Path sfdxProjectJson = projectPath.resolve("sfdx-project.json");
1✔
74

75
                // Limit analysis to SFDX Projects
76
                // MDAPI analysis is currently supported but is expected to be deprecated soon
77
                if (Files.isDirectory(projectPath) && Files.isRegularFile(sfdxProjectJson)) {
1!
78
                    org = Org.newOrg(rootDir.get());
1✔
79

80
                    // FIXME: Syntax & Semantic errors found during Org loading are not currently being reported. These
81
                    // should be routed to the new SemanticErrorReporter but that is not available for use just yet.
82
                    // Specifically we should check sfdx-project.json was ok as errors will disable further analysis
83
                    Issue[] projectErrors =
1✔
84
                            Arrays.stream(org.issues().issuesForFile(sfdxProjectJson.toString()))
1✔
85
                                    .filter(Issue::isError).toArray(Issue[]::new);
1✔
86
                    Arrays.stream(projectErrors).forEach(issue -> LOG.info(issue.toString()));
1✔
87
                    if (projectErrors.length != 0) {
1✔
88
                        org = null;
1✔
89
                    }
90
                } else {
1✔
91
                    LOG.info("Missing project file at {}", sfdxProjectJson);
1✔
92
                }
93
            }
UNCOV
94
        } catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {
×
95
            // Note: Org.newOrg() will try to find the base Apex Types through the current classloader
96
            // in package "com.nawforce.runforce". This requires, that directory listings can be retrieved
97
            // on the URL that the classloader returns from getResource("/com/nawforce/runforce"):
98
            // https://github.com/nawforce/apex-link/blob/7688adcb7a2d7f8aa28d0618ffb2a3aa81151858/apexlink/src/main/scala/com/nawforce/apexlink/types/platform/PlatformTypeDeclaration.scala#L260-L273
99
            // However, when running as an Eclipse plugin, we have a special bundle classloader, that returns
100
            // URIs in the form "bundleresource://...". For the schema "bundleresource", no FileSystemProvider can be
101
            // found, so we get a java.nio.file.ProviderNotFoundException. Since all this happens during initialization of the class
102
            // com.nawforce.apexlink.types.platform.PlatformTypeDeclaration we get a ExceptionInInitializerError
103
            // and later NoClassDefFoundErrors, because PlatformTypeDeclaration couldn't be loaded.
UNCOV
104
            LOG.error("Exception while initializing Apexlink ({})", e.getMessage(), e);
×
UNCOV
105
            LOG.error("PMD will not attempt to initialize Apexlink further, this can cause rules like UnusedMethod and AvoidInterfaceAsMapKey to be dysfunctional");
×
106
        }
1✔
107
        this.org = org;
1✔
108
    }
1✔
109

110
    /**
111
     * Returns true if this is analysis index is in a failed state.
112
     * This object is then useless. The failed instance is returned
113
     * from {@link ApexLanguageProcessor#getMultiFileState()} if
114
     * loading the org failed, maybe because of malformed configuration.
115
     */
116
    public boolean isFailed() {
117
        return org == null;
1✔
118
    }
119

120
    private void maybeWarnAboutMissingOrg() {
121
        if (isFailed() && !warnedAboutMissingOrg) {
1!
122
            warnedAboutMissingOrg = true;
1✔
123
            LOG.warn("Multifile analysis unavailable. "
1✔
124
                    + "Set PMD_APEX_ROOT_DIRECTORY to enable cross-file type resolution.");
125
        }
126
    }
1✔
127

128
    public List<Issue> getFileIssues(String filename) {
129
        maybeWarnAboutMissingOrg();
1✔
130
        // Extract issues for a specific metadata file from the org
131
        return org == null ? Collections.emptyList()
1✔
132
                           : Collections.unmodifiableList(Arrays.asList(org.issues().issuesForFile(filename)));
1✔
133
    }
134

135
    /**
136
     * Returns an unmodifiable list of all type summaries in the org.
137
     * Returns an empty list when multifile analysis is unavailable.
138
     * This enables rules to perform complex cross-type analysis.
139
     * @since 7.24.0
140
     */
141
    public List<TypeSummary> getTypeSummaries() {
142
        maybeWarnAboutMissingOrg();
1✔
143
        return getAllTypeSummaries();
1✔
144
    }
145

146
    /**
147
     * Returns a flat list of all TypeSummary objects in the org, including nested types.
148
     * The result is computed once and then cached.
149
     */
150
    private synchronized List<TypeSummary> getAllTypeSummaries() {
151
        if (allTypeSummaries != null) {
1✔
152
            return allTypeSummaries;
1✔
153
        }
154
        if (org == null) {
1✔
155
            allTypeSummaries = Collections.emptyList();
1✔
156
            return allTypeSummaries;
1✔
157
        }
158
        List<TypeSummary> summaries = new ArrayList<>();
1✔
159
        for (Package pkg : org.getPackages()) {
1✔
160
            for (TypeIdentifier typeId : pkg.getTypeIdentifiers(false)) {
1✔
161
                TypeSummary summary = pkg.getSummaryOfType(typeId);
1✔
162
                if (summary != null) {
1!
163
                    collectTypeSummaries(summary, summaries);
1✔
164
                }
165
            }
166
        }
167
        allTypeSummaries = Collections.unmodifiableList(summaries);
1✔
168
        return allTypeSummaries;
1✔
169
    }
170

171
    /** Adds the given summary and all its nested type summaries (recursively) to the list. */
172
    private static void collectTypeSummaries(TypeSummary summary, List<TypeSummary> result) {
173
        result.add(summary);
1✔
174
        scala.collection.Iterator<TypeSummary> nested = summary.nestedTypes().iterator();
1✔
175
        while (nested.hasNext()) {
1✔
176
            collectTypeSummaries(nested.next(), result);
1✔
177
        }
178
    }
1✔
179

180
    /*
181
     * Very simple logger to aid debugging, relays ApexLink logging into PMD
182
     */
183
    private static final class AnalysisLogger implements com.nawforce.pkgforce.diagnostics.Logger {
184

185
        @Override
186
        public void info(String message) {
UNCOV
187
            LOG.info(message);
×
UNCOV
188
        }
×
189

190
        @Override
191
        public void debug(String message) {
UNCOV
192
            LOG.debug(message);
×
UNCOV
193
        }
×
194

195
        @Override
196
        public void trace(String message) {
UNCOV
197
            LOG.trace(message);
×
UNCOV
198
        }
×
199
    }
200
}
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