use crate::dcf::deps::{dependency_entries, is_dependency_field};
use crate::linter::diagnostic::{Diagnostic, ViolationData};
use crate::linter::rules::{DcfRule, DcfRuleContext, Example};
pub struct DescriptionVersionConstraint;
const EXAMPLES: &[Example] = &[Example {
caption: "A version requirement R will not enforce:",
source: "Package: mypkg\nDepends: R (4.1)\nImports: dplyr (1.0.0)\n",
}];
impl DcfRule for DescriptionVersionConstraint {
fn id(&self) -> &'static str {
"description-version-constraint"
}
fn description(&self) -> &'static str {
"Flag a dependency entry whose parenthesized part is not a version \
constraint.\n\nR reads `pkg (>= 1.0.0)`: an operator and a version. \
Anything else—`dplyr (1.0.0)`, `dplyr (>=)`, `dplyr (latest)`—states no \
bound at all, and R's dependency check enforces nothing. The line \
reads as a requirement and behaves as none, so the package installs \
against exactly the versions its author meant to \
exclude.\n\nChecked in all five dependency fields, `R` included: \
`Depends: R (>= 4.1)` is the most common constraint in any \
`DESCRIPTION`.\n\nThere is no autofix. `dplyr (1.0.0)` most likely \
means `>=`, but it could mean `==` or `>`, and guessing would invent a \
requirement the author never wrote."
}
fn examples(&self) -> &'static [Example] {
EXAMPLES
}
fn check_file(&self, ctx: &DcfRuleContext<'_>, sink: &mut Vec<Diagnostic>) {
for field in ctx.document.fields() {
if !is_dependency_field(&field.name()) {
continue;
}
for entry in dependency_entries(&field) {
if !entry.malformed_constraint() {
continue;
}
let name = &entry.name;
sink.push(Diagnostic {
rule: "description-version-constraint",
severity: Default::default(),
path: Default::default(),
range: entry.range,
message: ViolationData::new(
"description-version-constraint",
format!(
"the version constraint on `{name}` states no bound, so R \
enforces nothing here"
),
)
.with_suggestion(
"Write a comparison operator and a version, as in `(>= 1.0.0)`."
.to_string(),
),
fix: None,
});
}
}
}
}