use crate::linter::diagnostic::{Diagnostic, Fix, ViolationData};
use crate::linter::rules::matchers::comment_deletion_span;
use crate::linter::rules::{Example, Rule, RuleContext, is_known_rule};
use crate::linter::suppression::{Directive, DirectiveUsage};
pub struct OutdatedSuppression;
const EXAMPLES: &[Example] = &[Example {
caption: "`x` is read, so `unused-binding` finds nothing and the directive is dead:",
source: "# arity-ignore unused-binding: no longer needed\nx <- 1\nprint(x)\n",
}];
impl Rule for OutdatedSuppression {
fn id(&self) -> &'static str {
"outdated-suppression"
}
fn description(&self) -> &'static str {
"Flags a `# arity-ignore` directive that suppressed nothing on this run \
— the code it was written for has changed, but the directive stayed. A stale \
suppression is misleading (it asserts arity is wrong at a spot where arity says \
nothing) and it is a trap: it will silence a real finding if the shape ever \
comes back. The fix deletes the directive.\
\n\nTo avoid reporting a directive that is merely *dormant*, the rule only \
fires when the rule the directive names actually ran — a rule excluded by \
`select`/`ignore`, or one that is off by default, leaves its directives alone \
— or when the directive is dangling, with no code after it to attach to. \
Directives naming no rule are left to `blanket-suppression`, and unknown rule \
IDs to `misnamed-suppression`."
}
fn examples(&self) -> &'static [Example] {
EXAMPLES
}
fn doc_select(&self) -> &'static [&'static str] {
&["unused-binding"]
}
fn check_suppressions(
&self,
ctx: &RuleContext<'_>,
used: &DirectiveUsage,
sink: &mut Vec<Diagnostic>,
) {
let source = ctx.root.text().to_string();
for (index, directive) in ctx.suppressions.directives().iter().enumerate() {
let Some(rule) = &directive.rule else {
continue;
};
if !is_known_rule(&rule.id) {
continue;
}
if used.is_used(index) {
continue;
}
if !directive.is_dangling() && !ctx.enabled_rules.contains(&rule.id) {
continue;
}
sink.push(report(&source, directive, &rule.id));
}
}
}
fn report(source: &str, directive: &Directive, rule: &str) -> Diagnostic {
let body = if directive.is_dangling() {
format!("`{rule}` is suppressed here, but no code follows this directive")
} else {
format!("`{rule}` reports nothing here; this suppression is no longer needed")
};
let (start, end) = comment_deletion_span(source, directive.comment);
Diagnostic {
rule: "outdated-suppression",
severity: Default::default(),
path: Default::default(),
range: directive.comment,
message: ViolationData::new("outdated-suppression", body),
fix: Some(Fix::safe(start, end, "", "Remove the suppression")),
}
}