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

pmd / pmd / 23

30 May 2025 04:25PM UTC coverage: 78.377% (-0.2%) from 78.601%
23

push

github

adangel
[core] Add rule to report unnecessary suppression comments/annotations (#5609)

Merge pull request #5609 from oowekyala:new-rule-UnnecessarySuppression

17712 of 23434 branches covered (75.58%)

Branch coverage included in aggregate %.

159 of 328 new or added lines in 22 files covered. (48.48%)

36 existing lines in 4 files now uncovered.

38902 of 48799 relevant lines covered (79.72%)

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.file.Path;
11
import java.util.ArrayList;
12
import java.util.Iterator;
13
import java.util.List;
14
import java.util.Properties;
15
import java.util.stream.Collectors;
16

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

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

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

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

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

81
    private List<String> rulesets;
82
    
83

84
    private String format;
85

86
    private int threads;
87

88
    private boolean benchmark;
89

90
    private boolean showSuppressed;
91

92
    private String suppressMarker;
93

94
    private RulePriority minimumPriority;
95

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

98
    private List<LanguageVersion> languageVersion;
99

100
    private Language forceLanguage;
101

102
    private String auxClasspath;
103

104
    private Path cacheLocation;
105

106
    private boolean noCache;
107

108
    private boolean showProgressBar;
109

110

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

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

123

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

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

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

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

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

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

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

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

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

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

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

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

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

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

237

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

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

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

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

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

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

287
    @Override
288
    @NonNull
289
    protected CliExitCode doExecute(PMDConfiguration configuration) {
290
        if (benchmark) {
1!
UNCOV
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✔
UNCOV
301
                } catch (final Exception e) {
×
UNCOV
302
                    pmdReporter.errorEx("Could not initialize analysis", e);
×
UNCOV
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 {
UNCOV
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

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

344
    private void printErrorDetected(PmdReporter reporter, int errors) {
UNCOV
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));
×
UNCOV
349
    }
×
350

351
    private void finishBenchmarker(final PmdReporter pmdReporter) {
352
        if (benchmark) {
1!
UNCOV
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")
UNCOV
361
                final Writer writer = new OutputStreamWriter(System.err);
×
UNCOV
362
                renderer.render(timingReport, writer);
×
UNCOV
363
            } catch (final IOException e) {
×
UNCOV
364
                pmdReporter.errorEx("Error producing benchmark report", e);
×
UNCOV
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
                
UNCOV
394
                for (final PropertyDescriptor<?> property : renderer.getPropertyDescriptors()) {
×
UNCOV
395
                    propertyNames.add(property.name());
×
UNCOV
396
                }
×
UNCOV
397
            }
×
UNCOV
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