Skip to content

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

Merged
adangel merged 34 commits intopmd:mainfrom
oowekyala:new-rule-UnnecessarySuppression
May 30, 2025
Merged

[core] Add rule to report unnecessary suppression comments/annotations#5609
adangel merged 34 commits intopmd:mainfrom
oowekyala:new-rule-UnnecessarySuppression

Conversation

@oowekyala
Copy link
Copy Markdown
Member

@oowekyala oowekyala commented Mar 19, 2025

Describe the PR

Improve ViolationSuppressor to keep track of used/unused suppressors. Create a rule in pmd-core that reports those unused things using the ViolationSuppressors. The rule is special cased to execute after all other rules. Its violations cannot be suppressed. The rule implementation can be shared between all languages, sort of like XPathRule. We could have a ruleset in pmd-core with just this rule predefined (eg category/core/bestpractices.xml), but I don't think it's the right way, even though it would save me some typing... For the purpose of having language specific tests and documentation, I will add a rule definition for each language, in category bestpractices.

Side note, there are 100 unnecessary comments/annotations in pmd-core and pmd-java. Cleanup is overdue :)

Related issues

Ready?

  • Added unit tests for fixed bug/feature
  • Passing all unit tests
  • Complete build ./mvnw clean verify passes (checked automatically by github actions)
  • Added (in-code) documentation (if needed)
  • Add rule for languages:
    • Java
    • Apex
    • PLSQL
    • XML
    • HTML
    • Modelica
    • ...
  • Add tests for annotations (Java, Apex)
  • Update documentation (page about suppression)
  • Cleanup
  • Update release notes
    • deprecations
    • announce new rule

@oowekyala oowekyala added this to the 7.12.0 milestone Mar 19, 2025
@github-actions

This comment was marked as outdated.

@jsotuyod
Copy link
Copy Markdown
Member

jsotuyod commented Mar 23, 2025

First off, thanks! I've long wanted to tackle this, it's a massive win; as shown by the number of findings it has. Spcially as we remove FPs over time that used to be suppressed.

As for this…

We could have a ruleset in pmd-core with just this rule predefined (eg category/core/bestpractices.xml), but I don't think it's the right way, even though it would save me some typing...

I'm actually not against it… in my long list of "maybe interesting ideas" was having a Universal AST (UAST) in the form of interfaces, that languages could implement in their corresponding nodes; and some rules could be rewritten in terms of the UAST as language-agnostic rules (ie: EmptyControlStatement only needs to know what nodes are control statements, and the number of children they have). This obviously has some deep implications (ie: needing to be able to transverse the AST based on interfaces, which is unsupported in XPath rules nor on node visitors); but ideally this would allow us to define the rule once in an abstract ruleset (ie: category/uast/codestyle.xml under pmd-core with a bogus language such as uast), and then have each language, under the appropriate category, such as category/java/codestyle.xml, include a <rule ref="category/uast/codestyle.xml/EmptyCatchBlock" language="java">. This makes it 100% an implementation detail, users can reference it through category/java/codestyle.xml/EmptyCatchBlock without ever knowing this is backed by a rule living somewhere else…

Ensuring a single implementation allows us to make sure any fixes / improvements are applied across the board too.

I see no reason why we wouldn't want to take such an approach here to be honest…
We can (and should) still have per-language tests.
Changes to doc generation may be required obviously.

@oowekyala oowekyala added the a:new-rule Proposal to add a new built-in rule label Mar 23, 2025
@oowekyala
Copy link
Copy Markdown
Member Author

oowekyala commented Mar 24, 2025

Thank you for the feedback.

Ensuring a single implementation allows us to make sure any fixes / improvements are applied across the board too.
I see no reason why we wouldn't want to take such an approach here to be honest…
We can (and should) still have per-language tests.
Changes to doc generation may be required obviously.

I think we are definitely going to share the implementation. But I'm not sure we should surface that to users, which is why one rule per language (which use the same rule class) is my preferred course for now. Actually for most language a default implementation that is in an internal ruleset would be enough though (with a reference as you show). I'm not sure it is possible though, as I don't think it is possible to override the language of a rule, and it is definitely not possible to define a rule that doesn't have a language for now.

One thing I want to do with this rule is also have a java-specific implementation that also covers more @SuppressWarnings things, like @SuppressWarnings("unchecked") for instance. Which is why I don't think we should surface "UAST" rules or rules that apply to any language as anything else than just several language-specific rules.

@adangel adangel self-requested a review March 25, 2025 19:29
@adangel adangel modified the milestones: 7.12.0, 7.13.0 Mar 27, 2025
Copy link
Copy Markdown
Member

@adangel adangel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for starting this effort! This will definitely help a lot to cleanup old suppressions...

But I'm not sure we should surface that to users

Having this implemented as an internal rule is IMHO ok - that's an implementation detail.
If we expose this as a rule, then there are other implications, that cannot be expressed in the ruleset (there is no defined execution order of the enabled rules, executing this rule alone doesn't work etc...).

What about having a feature toggle for this? So, you run "pmd check --report-unused-suppressions" - and internally, we will add the new internal rule to the analysis execution (like this happens now in this PR). We could then later still change this to be a different kind of rule or any other element (report summarizer, whatever, multi-file analysis aware rule, #5254, ...). How we implemented this is then hidden and can be changed.

As this is implemented as a rule, the results are reported as a rule violation. But as we have seen, these violations are special - they can't be suppressed. So maybe this "thing" (implemented now as a rule) doesn't produce RuleViolations, but something else, that is consumed by the reporters? That way, the reporter could put this into a special section and not mix it under the "normal" violations.
I'm just thinking out loud here...
That would mean, that we couple this suppression a bit more with the reporting infrastructure. But we have already some coupling: There is a feature toggle to report suppressed violations ("--show-suppressed").
Maybe we should add a new method in "FileAnalysisListener" to report such "special" violations (e.g. onUnusedSuppression)?

For documentation, we probably also should emphasize more the "suppression reason"/"user message" (see also #5454). And side note: When suppressing with annotation, it is not possible to provide a user message... But that's another topic. When we add such a functionality, then probably a rule "suppression without reason" could be useful.

Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstInfo.java
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstInfo.java Outdated
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstInfo.java
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstInfo.java Outdated
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstInfo.java Outdated
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java
Comment thread pmd-java/src/main/resources/category/java/bestpractices.xml Outdated
Comment thread pmd-java/src/main/resources/category/java/bestpractices.xml
Comment thread pmd-test/src/main/java/net/sourceforge/pmd/test/RuleTst.java
@oowekyala
Copy link
Copy Markdown
Member Author

What about having a feature toggle for this? So, you run "pmd check --report-unused-suppressions" - and internally, we will add the new internal rule to the analysis execution (like this happens now in this PR). We could then later still change this to be a different kind of rule or any other element (report summarizer, whatever, multi-file analysis aware rule, #5254, ...). How we implemented this is then hidden and can be changed.

I think there are very good reasons for having this as a rule.

  • For users, it is a check that adds violations to the ruleset. Those violations behave exactly like other violations (they may fail the build for instance). So for an external user, it looks like a rule. It would be surprising if it weren't a rule.

  • If we want to add configuration properties, we cannot do that easily with a CLI switch. But that is directly possible with rule properties.

  • Also a CLI switch needs to be surfaced in all other integrations (maven, gradle, ant, etc), whereas if it is just a part of your ruleset, the integration comes for free.

  • There is overlap between this check and another rule that reports unused @SuppressWarnings in general. That's because the same @SuppressWarnings annotation is used to suppress PMD violations and other kinds of warnings (eg, unchecked compiler warnings). It would be IMO very useful to report those (eg @SuppressWarnings("unchecked") when there is no such warning), and that would of course be a regular rule. But since it reports the same constructs, and therefore needs to parse annotations in the same way, it makes sense to centralize the logic for both in a single rule. This will avoid duplicate violations for instance.

    The boundaries between an "unused pmd warning suppression" check, and an "unused compiler warning suppression" is made even more blurry by [java] Full @SuppressWarnings support #1900. Some PMD rules support a suppression string that is also used by compiler warnings, like fallthrough or unused. If we were to split the checks we would have to duplicate work, and possibly end up with a dysfunctional rule. For instance, if you have

     @SuppressWarnings("unused") void foo(int x) {}

    and you run PMD without UnusedFormalParameterRule, but with --report-unused-suppression. What is the "report unused suppression" check going to do? Should it report the annotation because there was no PMD warning? Obviously it should not, because there is in fact an unused parameter, so the annotation is also suppressing the compiler/IDE warning. A violation here would be a false positive. So there is no way around making the "report-unused-suppression" check aware of language-specific compiler/ide warnings. If you make the check language-specific, then to me, the following are direct consequences:

    • Since your check already somewhat needs to understand compiler warnings to avoid reporting falso positives, the check is also the best place to implement the "unused compiler warning suppression" rule. The logic is exactly the same.
    • The simplest implementation of the check is a rule. This is because, the simplest way to make a language-specific extension to PMD is a rule. It could be in the appropriate category for the language, with documentation about what it supports, how to configure it, etc. If it isn't a rule, we have to invent a new extension mechanism for --report-unused-suppression.

there is no defined execution order of the enabled rules, executing this rule alone doesn't work etc...

About this point, I think it isn't as bad as you think. It is appropriate for this check to not be suppressible, otherwise you end up with recursive suppression... where does it end. The only constraint on this rule is that all violations that may be suppressed are emitted before this rule's execution. This defines an easy execution order, which should be completely transparent to users anyway: all rules that may be suppressed have to executed before all rules that may not be suppressed. There is no defined execution order within either of those two categories (as of now). There may be other rules that don't support suppressions in the future, maybe for instance, rules that don't report violations, but metric values, if we want to implement that. If we do make "not suppressible" applicable to more rules, then it is likely we want a marker interface to tag rules for instance.

@adangel
Copy link
Copy Markdown
Member

adangel commented Apr 25, 2025

Thanks for the explanations!

I think there are very good reasons for having this as a rule.

  • For users, it is a check that adds violations to the ruleset. Those violations behave exactly like other violations (they may fail the build for instance). So for an external user, it looks like a rule. It would be surprising if it weren't a rule.

Yes, I think you convinced me 😄 It makes sense to expose this as a rule/ruleviolations.

there is no defined execution order of the enabled rules, executing this rule alone doesn't work etc...

About this point, I think it isn't as bad as you think.

The only point here I have is: this is another special behavior for one special rule, that users need to be aware of - it differs from other rules. But true, it is logical and easy to understand, that this "report unnecessary suppression rule" needs to run at the end (after all other rules).

I'm ok to start with that. For now we don't need to expose this feature to other rules (for now "UnnecessaryPmdSuppressionRule" is the only rule with that special behavior). The generalization of this behavior would be part of #5254 .

FYI - I'm moving this PR to 7.14.0 now... and doing the release of 7.13.0.

@adangel adangel modified the milestones: 7.13.0, 7.14.0 Apr 25, 2025
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleSet.java
Comment thread pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleSet.java Outdated
Comment thread pmd-java/src/main/resources/category/java/bestpractices.xml
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2025

Documentation Preview

Compared to main:
This changeset changes 0 violations,
introduces 109 new violations, 0 new errors and 0 new configuration errors,
removes 0 violations, 0 errors and 0 configuration errors.

Regression Tester Report

(comment created at 2025-05-30 16:04:12+00:00 for d24775b)

Copy link
Copy Markdown
Member

@adangel adangel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

This is basically, ready, isn't it?

I'll try to write up the release notes then and merge for the release tomorrow. As this is a experimental feature for now, I'd say it's fine, if it is not yet well documented on only available for java.

@oowekyala
Copy link
Copy Markdown
Member Author

I just wonder if the new violations are not mostly FPs... They are related to igniring with "unused". I had to build a check that tries to find any unused thing, but for instance I assumed that public methods cannot be reported as unused (although, that depends on your ide settings I guess), and so some public methods are reported here. Is this acceptable? Since violations of this rule cannot be suppressed we need to not have any FPs ^^

Comment thread docs/pages/release_notes.md
Comment thread docs/pages/release_notes.md Outdated
@adangel adangel marked this pull request as ready for review May 30, 2025 16:07
@adangel
Copy link
Copy Markdown
Member

adangel commented May 30, 2025

I just wonder if the new violations are not mostly FPs... They are related to igniring with "unused". I had to build a check that tries to find any unused thing, but for instance I assumed that public methods cannot be reported as unused (although, that depends on your ide settings I guess), and so some public methods are reported here. Is this acceptable? Since violations of this rule cannot be suppressed we need to not have any FPs ^^

I think, this now as good as it gets now. We have a lot violations about unused suppressions on private static inner classes - which look on first glance legit. I think, this all can be improved later on - after all: we explicitly say, this is experimental. And this rule is not enabled by default.

@adangel adangel merged commit 3bd234c into pmd:main May 30, 2025
36 checks passed
@oowekyala oowekyala deleted the new-rule-UnnecessarySuppression branch May 30, 2025 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a:new-rule Proposal to add a new built-in rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[core] Warn on unneeded suppression

3 participants