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

pmd / pmd / 277

27 Nov 2025 01:37PM UTC coverage: 78.778% (+0.03%) from 78.749%
277

push

github

adangel
[java] UseArraysAsList: skip when if-statements (#6228)

18419 of 24233 branches covered (76.01%)

Branch coverage included in aggregate %.

40090 of 50038 relevant lines covered (80.12%)

0.81 hits per line

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

68.72
/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/PmdCommand.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.cli.commands.internal;
6

7
import java.io.IOException;
8
import java.io.OutputStreamWriter;
9
import java.io.Writer;
10
import java.nio.charset.Charset;
11
import java.nio.file.Path;
12
import java.util.ArrayList;
13
import java.util.Iterator;
14
import java.util.List;
15
import java.util.Properties;
16
import java.util.stream.Collectors;
17

18
import org.checkerframework.checker.nullness.qual.NonNull;
19
import org.slf4j.Logger;
20
import org.slf4j.LoggerFactory;
21

22
import net.sourceforge.pmd.PMDConfiguration;
23
import net.sourceforge.pmd.PmdAnalysis;
24
import net.sourceforge.pmd.benchmark.TextTimingReportRenderer;
25
import net.sourceforge.pmd.benchmark.TimeTracker;
26
import net.sourceforge.pmd.benchmark.TimingReport;
27
import net.sourceforge.pmd.benchmark.TimingReportRenderer;
28
import net.sourceforge.pmd.cli.commands.typesupport.internal.NumThreadsConverter;
29
import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageTypeSupport;
30
import net.sourceforge.pmd.cli.commands.typesupport.internal.PmdLanguageVersionTypeSupport;
31
import net.sourceforge.pmd.cli.commands.typesupport.internal.RulePriorityTypeSupport;
32
import net.sourceforge.pmd.cli.internal.CliExitCode;
33
import net.sourceforge.pmd.cli.internal.ProgressBarListener;
34
import net.sourceforge.pmd.internal.LogMessages;
35
import net.sourceforge.pmd.lang.Language;
36
import net.sourceforge.pmd.lang.LanguageVersion;
37
import net.sourceforge.pmd.lang.rule.RulePriority;
38
import net.sourceforge.pmd.properties.PropertyDescriptor;
39
import net.sourceforge.pmd.renderers.Renderer;
40
import net.sourceforge.pmd.renderers.RendererFactory;
41
import net.sourceforge.pmd.reporting.ReportStats;
42
import net.sourceforge.pmd.util.StringUtil;
43
import net.sourceforge.pmd.util.log.PmdReporter;
44
import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter;
45

46
import picocli.CommandLine;
47
import picocli.CommandLine.Command;
48
import picocli.CommandLine.Option;
49
import picocli.CommandLine.ParameterException;
50

51
@Command(name = "check", showDefaultValues = true,
52
    description = "The PMD standard source code analyzer")
53
public class PmdCommand extends AbstractAnalysisPmdSubcommand<PMDConfiguration> {
1✔
54
    private static final Logger LOG = LoggerFactory.getLogger(PmdCommand.class);
1✔
55

56
    static {
57
        final Properties emptyProps = new Properties();
1✔
58
        final StringBuilder reportPropertiesHelp = new StringBuilder();
1✔
59
        final String lineSeparator = System.lineSeparator();
1✔
60
        
61
        for (final String rendererName : RendererFactory.supportedRenderers()) {
1✔
62
            final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps);
1✔
63
            
64
            if (!renderer.getPropertyDescriptors().isEmpty()) {
1✔
65
                reportPropertiesHelp.append(rendererName + ":" + lineSeparator);
1✔
66
                for (final PropertyDescriptor<?> property : renderer.getPropertyDescriptors()) {
1✔
67
                    reportPropertiesHelp.append("  ").append(property.name()).append(" - ")
1✔
68
                        .append(property.description()).append(lineSeparator);
1✔
69
                    final Object deflt = property.defaultValue();
1✔
70
                    if (deflt != null && !"".equals(deflt)) {
1!
71
                        reportPropertiesHelp.append("    Default: ").append(deflt)
1✔
72
                            .append(lineSeparator);
1✔
73
                    }
74
                }
1✔
75
            }
76
        }
1✔
77
        
78
        // System Properties are the easier way to inject dynamically computed values into the help of an option
79
        System.setProperty("pmd-cli.pmd.report.properties.help", reportPropertiesHelp.toString());
1✔
80
    }
1✔
81

82
    private List<String> rulesets;
83
    
84

85
    private String format;
86

87
    private int threads;
88

89
    private boolean benchmark;
90

91
    private boolean showSuppressed;
92

93
    private String suppressMarker;
94

95
    private RulePriority minimumPriority;
96

97
    private Properties properties = new Properties();
1✔
98

99
    private List<LanguageVersion> languageVersion;
100

101
    private Language forceLanguage;
102

103
    private String auxClasspath;
104

105
    private Path cacheLocation;
106

107
    private boolean noCache;
108

109
    private boolean showProgressBar;
110

111

112
    @CommandLine.ArgGroup(heading = FILE_COLLECTION_OPTION_HEADER, exclusive = false)
1✔
113
    FileCollectionOptions<PMDConfiguration> files = new FileCollectionOptions<>();
114

115
    @Option(names = { "--rulesets", "-R" },
116
               description = "Path to a ruleset xml file. "
117
                             + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. "
118
                             + "The option can be repeated, and multiple arguments separated by comma can be provided to a single occurrence of the option.",
119
               required = true, split = ",", arity = "1..*")
120
    public void setRulesets(final List<String> rulesets) {
121
        this.rulesets = rulesets;
1✔
122
    }
1✔
123

124

125
    @Option(names = { "--format", "-f" },
126
            description = "Report format.%nValid values: ${COMPLETION-CANDIDATES}%n"
127
                    + "Alternatively, you can provide the fully qualified name of a custom Renderer in the classpath.",
128
            defaultValue = "text", completionCandidates = PmdSupportedReportFormatsCandidates.class)
129
    public void setFormat(final String format) {
130
        this.format = format;
1✔
131
    }
1✔
132

133
    @Option(names = { "--benchmark", "-b" },
134
            description = "Benchmark mode - output a benchmark report upon completion; default to System.err.")
135
    public void setBenchmark(final boolean benchmark) {
136
        this.benchmark = benchmark;
×
137
    }
×
138

139
    @Option(names = "--show-suppressed", description = "Report should show suppressed rule violations if supported by the report format.")
140
    public void setShowSuppressed(final boolean showSuppressed) {
141
        this.showSuppressed = showSuppressed;
×
142
    }
×
143

144
    @Option(names = "--suppress-marker",
145
            description = "Specifies the string that marks a line which PMD should ignore.",
146
            defaultValue = "NOPMD")
147
    public void setSuppressMarker(final String suppressMarker) {
148
        this.suppressMarker = suppressMarker;
1✔
149
    }
1✔
150

151
    @Option(names = "--minimum-priority",
152
            description = "Rule priority threshold; rules with lower priority than configured here won't be used.%n"
153
                    + "Valid values (case insensitive): ${COMPLETION-CANDIDATES}",
154
            defaultValue = "Low",
155
            completionCandidates = RulePriorityTypeSupport.class, converter = RulePriorityTypeSupport.class)
156
    public void setMinimumPriority(final RulePriority priority) {
157
        this.minimumPriority = priority;
1✔
158
    }
1✔
159

160
    @Option(names = { "--property", "-P" }, description = "Key-value pair defining a property for the report format.%n"
161
                + "Supported values for each report format:%n${sys:pmd-cli.pmd.report.properties.help}",
162
            completionCandidates = PmdReportPropertiesCandidates.class)
163
    public void setProperties(final Properties properties) {
164
        this.properties = properties;
×
165
    }
×
166

167
    @Option(names = "--use-version",
168
            description = "The language version PMD should use when parsing source code.%nValid values: ${COMPLETION-CANDIDATES}",
169
            completionCandidates = PmdLanguageVersionTypeSupport.class, converter = PmdLanguageVersionTypeSupport.class)
170
    public void setLanguageVersion(final List<LanguageVersion> languageVersion) {
171
        // Make sure we only set 1 version per language
172
        languageVersion.stream().collect(Collectors.groupingBy(LanguageVersion::getLanguage))
1✔
173
            .forEach((l, list) -> {
1✔
174
                if (list.size() > 1) {
1!
175
                    throw new ParameterException(spec.commandLine(), "Can only set one version per language, "
×
176
                            + "but for language " + l.getName() + " multiple versions were provided "
×
177
                            + list.stream().map(LanguageVersion::getTerseName).collect(Collectors.joining("', '", "'", "'")));
×
178
                }
179
            });
1✔
180

181
        this.languageVersion = languageVersion;
1✔
182
    }
1✔
183

184
    @Option(names = "--force-language",
185
            description = "Force a language to be used for all input files, irrespective of file names. "
186
                          + "When using this option, the automatic language selection by extension is disabled, and PMD "
187
                          + "tries to parse all input files with the given language's parser. "
188
                          + "Parsing errors are ignored.%nValid values: ${COMPLETION-CANDIDATES}",
189
            completionCandidates = PmdLanguageTypeSupport.class, converter = PmdLanguageTypeSupport.class)
190
    public void setForceLanguage(final Language forceLanguage) {
191
        this.forceLanguage = forceLanguage;
1✔
192
    }
1✔
193

194
    @Option(names = "--aux-classpath",
195
            description = "Specifies the classpath for libraries used by the source code. "
196
                    + "This is used to resolve types in Java source files. The platform specific path delimiter "
197
                    + "(\":\" on Linux, \";\" on Windows) is used to separate the entries. "
198
                    + "Alternatively, a single 'file:' URL to a text file containing path elements on consecutive lines "
199
                    + "can be specified.")
200
    public void setAuxClasspath(final String auxClasspath) {
201
        this.auxClasspath = auxClasspath;
×
202
    }
×
203

204
    @Option(names = "--cache",
205
            description = "Specify the location of the cache file for incremental analysis. "
206
                    + "This should be the full path to the file, including the desired file name (not just the parent directory). "
207
                    + "If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run "
208
                    + "with the most up-to-date rule violations.")
209
    public void setCacheLocation(final Path cacheLocation) {
210
        this.cacheLocation = cacheLocation;
×
211
    }
×
212

213
    @Option(names = "--no-cache", description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.")
214
    public void setNoCache(final boolean noCache) {
215
        this.noCache = noCache;
1✔
216
    }
1✔
217

218
    @Option(names = {"--threads", "-t"}, description =
219
        "Set the number of threads used by PMD. This can be an integer, or a float (or int) followed by the letter `C`, eg `0.5C` or `1C`. "
220
            + "In the latter case, the float will be multiplied by the number of cores of the host machine, and rounded down to an integer. "
221
            + "If the specified number of threads is zero, then PMD will use the main thread for everything. If it is `n` > 0, "
222
            + "PMD will spawn `n` separate analysis threads besides the main thread.",
223
        defaultValue = "1C", converter = NumThreadsConverter.class)
224
    public void setThreads(final int threads) {
225
        if (threads < 0) {
1!
226
            throw new ParameterException(spec.commandLine(), "Thread count should be a positive number or zero, found " + threads + " instead.");
×
227
        }
228
        
229
        this.threads = threads;
1✔
230
    }
1✔
231

232
    @Option(names = "--no-progress", negatable = true, defaultValue = "true",
233
            description = "Enables / disables progress bar indicator of live analysis progress.")
234
    public void setShowProgressBar(final boolean showProgressBar) {
235
        this.showProgressBar = showProgressBar;
1✔
236
    }
1✔
237

238

239
    @Override
240
    protected FileCollectionOptions<PMDConfiguration> getFileCollectionOptions() {
241
        return files;
1✔
242
    }
243

244
    /**
245
     * Converts these parameters into a configuration.
246
     *
247
     * @return A new PMDConfiguration corresponding to these parameters
248
     *
249
     * @throws ParameterException if the parameters are inconsistent or incomplete
250
     */
251
    @Override
252
    protected PMDConfiguration toConfiguration() {
253
        final PMDConfiguration configuration = new PMDConfiguration();
1✔
254
        setCommonConfigProperties(configuration);
1✔
255

256
        configuration.setReportFormat(format);
1✔
257
        configuration.setMinimumPriority(minimumPriority);
1✔
258
        configuration.setReportProperties(properties);
1✔
259
        configuration.setRuleSets(rulesets);
1✔
260
        configuration.setShowSuppressedViolations(showSuppressed);
1✔
261
        configuration.setSuppressMarker(suppressMarker);
1✔
262
        configuration.setAnalysisCacheLocation(cacheLocation != null ? cacheLocation.toString() : null);
1!
263
        configuration.setIgnoreIncrementalAnalysis(noCache);
1✔
264
        configuration.setThreads(threads);
1✔
265

266
        if (languageVersion != null) {
1✔
267
            configuration.setDefaultLanguageVersions(languageVersion);
1✔
268
        }
269
        
270
        // Important: do this after setting default versions, so we can pick them up
271
        if (forceLanguage != null) {
1✔
272
            final LanguageVersion forcedLangVer = configuration.getLanguageVersionDiscoverer()
1✔
273
                    .getDefaultLanguageVersion(forceLanguage);
1✔
274
            configuration.setForceLanguageVersion(forcedLangVer);
1✔
275
        }
276

277
        // Setup CLI message reporter
278
        configuration.setReporter(new SimpleMessageReporter(LoggerFactory.getLogger(PmdCommand.class)));
1✔
279

280
        try {
281
            configuration.prependAuxClasspath(auxClasspath);
1✔
282
        } catch (IllegalArgumentException e) {
×
283
            throw new ParameterException(spec.commandLine(), "Invalid auxiliary classpath: " + e.getMessage(), e);
×
284
        }
1✔
285
        return configuration;
1✔
286
    }
287

288
    @Override
289
    protected @NonNull CliExitCode doExecute(PMDConfiguration configuration) {
290
        if (benchmark) {
1!
291
            TimeTracker.startGlobalTracking();
×
292
        }
293

294
        final PmdReporter pmdReporter = configuration.getReporter();
1✔
295

296
        try {
297
            PmdAnalysis pmd = null;
1✔
298
            try {
299
                try {
300
                    pmd = PmdAnalysis.create(configuration);
1✔
301
                } catch (final Exception e) {
×
302
                    pmdReporter.errorEx("Could not initialize analysis", e);
×
303
                    return CliExitCode.ERROR;
×
304
                }
1✔
305

306
                LOG.debug("Runtime classpath:\n{}", System.getProperty("java.class.path"));
1✔
307
                LOG.debug("Aux classpath: {}", configuration.getClassLoader());
1✔
308

309
                if (showProgressBar) {
1✔
310
                    if (configuration.getReportFilePath() == null) {
1!
311
                        pmdReporter.warn("Progressbar rendering conflicts with reporting to STDOUT. "
1✔
312
                                + "No progressbar will be shown. Try running with argument '-r <file>' to output the report to a file instead.");
313
                    } else {
314
                        pmd.addListener(new ProgressBarListener());
×
315
                    }
316
                }
317

318
                final ReportStats stats = pmd.runAndReturnStats();
1✔
319
                if (pmdReporter.numErrors() > 0) {
1✔
320
                    // processing errors are ignored
321
                    return CliExitCode.ERROR;
1✔
322
                } else if (stats.getNumErrors() > 0 && configuration.isFailOnError()) {
1✔
323
                    return CliExitCode.RECOVERED_ERRORS_OR_VIOLATIONS;
1✔
324
                } else if (stats.getNumViolations() > 0 && configuration.isFailOnViolation()) {
1✔
325
                    return CliExitCode.VIOLATIONS_FOUND;
1✔
326
                } else {
327
                    return CliExitCode.OK;
1✔
328
                }
329
            } finally {
330
                if (pmd != null) {
1!
331
                    pmd.close();
1✔
332
                }
333
            }
334

335
        } catch (final Exception e) {
×
336
            pmdReporter.errorEx("Exception while running PMD.", e);
×
337
            printErrorDetected(pmdReporter, 1);
×
338
            return CliExitCode.ERROR;
×
339
        } finally {
340
            finishBenchmarker(pmdReporter);
1✔
341
        }
342
    }
343

344
    private void printErrorDetected(PmdReporter reporter, int errors) {
345
        String msg = LogMessages.errorDetectedMessage(errors, "pmd");
×
346
        // note: using error level here increments the error count of the reporter,
347
        // which we don't want.
348
        reporter.info(StringUtil.quoteMessageFormat(msg));
×
349
    }
×
350

351
    private void finishBenchmarker(final PmdReporter pmdReporter) {
352
        if (benchmark) {
1!
353
            final TimingReport timingReport = TimeTracker.stopGlobalTracking();
×
354

355
            // TODO get specified report format from config
356
            final TimingReportRenderer renderer = new TextTimingReportRenderer();
×
357

358
            try {
359
                // No try-with-resources, do not want to close STDERR
360
                @SuppressWarnings("PMD.CloseResource")
361
                final Writer writer = new OutputStreamWriter(System.err, Charset.defaultCharset());
×
362
                renderer.render(timingReport, writer);
×
363
            } catch (final IOException e) {
×
364
                pmdReporter.errorEx("Error producing benchmark report", e);
×
365
            }
×
366
        }
367
    }
1✔
368

369
    /**
370
     * Provider of candidates for valid report formats.
371
     */
372
    private static final class PmdSupportedReportFormatsCandidates implements Iterable<String> {
373

374
        @Override
375
        public Iterator<String> iterator() {
376
            return RendererFactory.supportedRenderers().iterator();
1✔
377
        }
378
    }
379

380
    /**
381
     * Provider of candidates for valid report properties.
382
     * 
383
     * Check the help for which ones are supported by each report format and possible values.
384
     */
385
    private static final class PmdReportPropertiesCandidates implements Iterable<String> {
386

387
        @Override
388
        public Iterator<String> iterator() {
389
            final List<String> propertyNames = new ArrayList<>();
×
390
            final Properties emptyProps = new Properties();
×
391
            for (final String rendererName : RendererFactory.supportedRenderers()) {
×
392
                final Renderer renderer = RendererFactory.createRenderer(rendererName, emptyProps);
×
393
                
394
                for (final PropertyDescriptor<?> property : renderer.getPropertyDescriptors()) {
×
395
                    propertyNames.add(property.name());
×
396
                }
×
397
            }
×
398
            return propertyNames.iterator();
×
399
        }
400
    }
401
}
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