use super::*;
const VALID_KWARGS: &[&str] = &["help", "long", "short", "value", "pattern"];
define_rule! {
ArgAttributeRule {
id: "arg-attribute",
message: "invalid arg attribute",
run(context) {
let Some(tree) = context.tree() else {
return Vec::new();
};
tree
.root_node()
.find_all("attribute")
.into_iter()
.flat_map(|attribute| {
attribute
.find_all("^identifier")
.into_iter()
.filter(|node| context.document().get_node_text(node) == "arg")
.flat_map(move |identifier| Self::validate(context, attribute, identifier))
.collect::<Vec<_>>()
})
.collect()
}
}
}
impl ArgAttributeRule {
fn parameter_unknown(
context: &RuleContext,
attribute: Node,
parameter_name: &str,
) -> bool {
let Some(recipe_node) = attribute.get_parent("recipe") else {
return false;
};
let Some(name_node) = recipe_node.find("recipe_header > identifier") else {
return false;
};
let recipe_name = context.document().get_node_text(&name_node);
let Some(recipe) = context.recipe(&recipe_name) else {
return false;
};
!recipe
.parameters
.iter()
.any(|parameter| parameter.name == parameter_name)
}
fn validate(
context: &RuleContext,
attribute: Node,
identifier: Node,
) -> Vec<Diagnostic> {
let document = context.document();
let positional = identifier
.siblings()
.take_while(|node| node.kind() != "identifier")
.filter(|node| {
node.kind() == "expression" && node.start_byte() != node.end_byte()
})
.collect::<Vec<_>>();
let kwargs = identifier
.siblings()
.take_while(|node| node.kind() != "identifier")
.filter(|node| node.kind() == "attribute_named_param")
.collect::<Vec<_>>();
let Some(name_node) = positional.first().copied() else {
return Vec::new();
};
let parameter_name = document
.get_node_text(&name_node)
.trim_matches(|c| c == '\'' || c == '"')
.to_string();
let unknown_parameter =
Self::parameter_unknown(context, attribute, ¶meter_name).then(|| {
Diagnostic::error(
format!("`[arg]` references unknown parameter `{parameter_name}`"),
name_node.get_range(document),
)
});
let unknown_kwargs = kwargs.iter().filter_map(|node| {
let name = node
.child_by_field_name("name")
.map(|node| document.get_node_text(&node))?;
(!VALID_KWARGS.contains(&name.as_str())).then(|| {
Diagnostic::error(
format!(
"Unknown `[arg]` keyword `{name}`, expected one of {}",
VALID_KWARGS.join(", ")
),
node.get_range(document),
)
})
});
let kwarg_name = |node: &Node| {
node
.child_by_field_name("name")
.map(|node| document.get_node_text(&node))
};
let value_without_option = kwargs
.iter()
.find(|node| kwarg_name(node).as_deref() == Some("value"))
.filter(|_| {
!kwargs.iter().any(|node| {
matches!(kwarg_name(node).as_deref(), Some("long" | "short"))
})
})
.map(|node| {
Diagnostic::error(
"`[arg]` `value=` requires `long=` or `short=`".to_string(),
node.get_range(document),
)
});
unknown_parameter
.into_iter()
.chain(unknown_kwargs)
.chain(value_without_option)
.collect()
}
}