use rpm_spec::ast::{Span, SpecFile, Tag};
use crate::diagnostic::{Diagnostic, LintCategory, Severity};
use crate::lint::{Lint, LintMetadata};
use crate::rules::util::{collect_dep_atoms_in_items, iter_packages};
use crate::visit::Visit;
pub static METADATA: LintMetadata = LintMetadata {
id: "RPM040",
name: "self-conflict",
description: "A package declares a Conflicts entry naming itself, which blocks installation.",
default_severity: Severity::Deny,
category: LintCategory::Correctness,
};
#[derive(Debug, Default)]
pub struct SelfConflict {
diagnostics: Vec<Diagnostic>,
}
impl SelfConflict {
pub fn new() -> Self {
Self::default()
}
}
impl<'ast> Visit<'ast> for SelfConflict {
fn visit_spec(&mut self, spec: &'ast SpecFile<Span>) {
for pkg in iter_packages(spec) {
let Some(name) = pkg.name() else {
continue;
};
let conflicts =
collect_dep_atoms_in_items(pkg.items(), |t| matches!(t, Tag::Conflicts));
for atom in conflicts {
if atom.name.literal_str() == Some(name) {
self.diagnostics.push(
Diagnostic::new(
&METADATA,
Severity::Deny,
format!("package `{name}` conflicts with itself"),
pkg.header_span(),
)
.with_label(pkg.header_span(), "package declared here"),
);
}
}
}
}
}
impl Lint for SelfConflict {
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 = SelfConflict::new();
lint.visit_spec(&outcome.spec);
lint.take_diagnostics()
}
#[test]
fn flags_main_self_conflict() {
let diags = run("Name: hello\nConflicts: hello\n");
assert_eq!(diags.len(), 1);
assert_eq!(diags[0].lint_id, "RPM040");
}
#[test]
fn silent_when_conflicting_with_other_package() {
assert!(run("Name: hello\nConflicts: old-other\n").is_empty());
}
#[test]
fn flags_subpackage_self_conflict() {
let diags = run("Name: main\n\
%package -n foo\n\
Conflicts: foo\n\
%description -n foo\nbody\n");
assert_eq!(diags.len(), 1);
assert!(diags[0].message.contains("foo"));
}
#[test]
fn flags_subpackage_relative_self_conflict() {
let src = "Name: main\n\
%package devel\n\
Summary: dev files\n\
Conflicts: main-devel\n\
%description devel\nbody\n";
let diags = run(src);
assert_eq!(diags.len(), 1, "got {diags:?}");
assert!(diags[0].message.contains("main-devel"));
}
}