use rpm_spec::ast::{PreambleItem, Span, SpecFile, Tag, TagValue};
use crate::diagnostic::{Applicability, Diagnostic, LintCategory, Severity, Suggestion};
use crate::lint::{Lint, LintMetadata};
use crate::rules::util::{drop_span, iter_packages};
use crate::visit::Visit;
pub static METADATA: LintMetadata = LintMetadata {
id: "RPM035",
name: "useless-explicit-provides",
description: "Explicit `Provides:` of the package's own name is redundant with rpm's auto-provides.",
default_severity: Severity::Warn,
category: LintCategory::Correctness,
};
#[derive(Debug, Default)]
pub struct UselessExplicitProvides {
diagnostics: Vec<Diagnostic>,
}
impl UselessExplicitProvides {
pub fn new() -> Self {
Self::default()
}
}
impl<'ast> Visit<'ast> for UselessExplicitProvides {
fn visit_spec(&mut self, spec: &'ast SpecFile<Span>) {
for pkg in iter_packages(spec) {
let Some(name) = pkg.name() else {
continue;
};
for item in pkg.items() {
if !matches!(item.tag, Tag::Provides) {
continue;
}
if is_unversioned_provides_of(item, name) {
self.diagnostics.push(
Diagnostic::new(
&METADATA,
Severity::Warn,
format!("`Provides: {name}` is implied by rpm auto-provides"),
item.data,
)
.with_suggestion(Suggestion::new(
"remove the redundant Provides line",
vec![drop_span(item.data)],
Applicability::MachineApplicable,
)),
);
}
}
}
}
}
fn is_unversioned_provides_of(item: &PreambleItem<Span>, name: &str) -> bool {
let TagValue::Dep(rpm_spec::ast::DepExpr::Atom(atom)) = &item.value else {
return false;
};
atom.constraint.is_none() && atom.name.literal_str() == Some(name)
}
impl Lint for UselessExplicitProvides {
fn metadata(&self) -> &'static LintMetadata {
&METADATA
}
fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
std::mem::take(&mut self.diagnostics)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::parse;
fn run(src: &str) -> Vec<Diagnostic> {
let outcome = parse(src);
let mut lint = UselessExplicitProvides::new();
lint.visit_spec(&outcome.spec);
lint.take_diagnostics()
}
#[test]
fn flags_unversioned_self_provides() {
let diags = run("Name: hello\nProvides: hello\n");
assert_eq!(diags.len(), 1);
assert_eq!(diags[0].lint_id, "RPM035");
assert!(!diags[0].suggestions.is_empty());
}
#[test]
fn silent_for_versioned_self_provides() {
assert!(run("Name: hello\nProvides: hello = 1.0\n").is_empty());
}
#[test]
fn silent_for_other_package_provides() {
assert!(run("Name: hello\nProvides: virtual-thing\n").is_empty());
}
#[test]
fn flags_subpackage_useless_provides() {
let diags = run("Name: main\n\
%package -n foo\n\
Provides: foo\n\
%description -n foo\nbody\n");
assert_eq!(diags.len(), 1);
}
#[test]
fn flags_first_atom_of_mixed_provides_line() {
let src = "Name: hello\nProvides: hello, virt\n";
let diags = run(src);
assert_eq!(diags.len(), 1);
assert!(diags[0].message.contains("hello"));
}
}