use super::*;
#[derive(Clone, Copy, Debug)]
enum DuplicateScope {
Module,
Recipe,
}
#[derive(Clone, Copy, Debug)]
enum DuplicateKey {
Argument,
Name,
}
#[derive(Debug)]
struct DuplicateConstraint {
key: DuplicateKey,
name: &'static str,
scope: DuplicateScope,
}
const DUPLICATE_CONSTRAINTS: &[DuplicateConstraint] = &[
DuplicateConstraint {
name: "default",
scope: DuplicateScope::Module,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "confirm",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "doc",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "exit-message",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "env",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Argument,
},
DuplicateConstraint {
name: "extension",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "group",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Argument,
},
DuplicateConstraint {
name: "dragonfly",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "freebsd",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "linux",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "macos",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "no-cd",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "no-exit-message",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "no-quiet",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "netbsd",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "openbsd",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "parallel",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "positional-arguments",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "private",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "script",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "unix",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "windows",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
DuplicateConstraint {
name: "working-directory",
scope: DuplicateScope::Recipe,
key: DuplicateKey::Name,
},
];
define_rule! {
DuplicateAttributeRule {
id: "duplicate-attribute",
message: "duplicate attribute",
run(context) {
let mut diagnostics = Vec::new();
let mut module_seen: HashMap<&'static str, HashSet<String>> =
HashMap::new();
for recipe in context.recipes() {
let mut recipe_seen: HashMap<&'static str, HashSet<String>> =
HashMap::new();
for attribute in &recipe.attributes {
let attribute_name = attribute.name.value.as_str();
let Some(constraint) = DuplicateAttributeRule::constraint(attribute_name) else {
continue;
};
let Some(key) = DuplicateAttributeRule::key(constraint, attribute) else {
continue;
};
let seen = match constraint.scope {
DuplicateScope::Module => {
module_seen.entry(constraint.name).or_default()
}
DuplicateScope::Recipe => {
recipe_seen.entry(constraint.name).or_default()
}
};
if !seen.insert(key.clone()) {
diagnostics.push(Diagnostic::error(
DuplicateAttributeRule::message(constraint, recipe),
attribute.range,
));
}
}
}
diagnostics
}
}
}
impl DuplicateAttributeRule {
fn constraint(name: &str) -> Option<&'static DuplicateConstraint> {
DUPLICATE_CONSTRAINTS
.iter()
.find(|constraint| constraint.name == name)
}
fn key(
constraint: &DuplicateConstraint,
attribute: &Attribute,
) -> Option<String> {
match constraint.key {
DuplicateKey::Name => Some(attribute.name.value.clone()),
DuplicateKey::Argument => attribute
.arguments
.first()
.map(|argument| argument.value.clone()),
}
}
fn message(constraint: &DuplicateConstraint, recipe: &Recipe) -> String {
match constraint.scope {
DuplicateScope::Module => format!(
"Recipe `{}` has duplicate `[{}]` attribute, which may only appear once per module",
recipe.name.value, constraint.name
),
DuplicateScope::Recipe => {
format!("Recipe attribute `{}` is duplicated", constraint.name)
}
}
}
}