use helm_schema_ast::{TemplateExpr, TemplateHeader, children_with_field};
use crate::analysis_db::IrAnalysisDb;
use crate::node_eval::{NodeAction, else_if_pairs, node_action};
pub(crate) struct LiteralDispatchArm {
pub(crate) header: Option<TemplateHeader>,
pub(crate) literal: String,
pub(crate) raw_empty: bool,
}
pub(crate) fn helper_literal_dispatch(
db: &IrAnalysisDb,
name: &str,
) -> Option<Vec<LiteralDispatchArm>> {
let body = db.parsed_helper_body(name)?;
let mut dispatch = None;
if !collect_dispatch(body.source, body.tree.root_node(), &mut dispatch) {
return None;
}
dispatch
}
fn collect_dispatch(
source: &str,
node: tree_sitter::Node<'_>,
dispatch: &mut Option<Vec<LiteralDispatchArm>>,
) -> bool {
let mut walker = node.walk();
for child in node.named_children(&mut walker) {
match node_action(source, child) {
NodeAction::Text => {
if child
.utf8_text(source.as_bytes())
.is_ok_and(|text| !text.trim().is_empty())
{
return false;
}
}
NodeAction::Suppressed => {}
NodeAction::If(header) => {
if dispatch.is_some() {
return false;
}
let Some(arms) = dispatch_arms(source, child, header) else {
return false;
};
*dispatch = Some(arms);
}
NodeAction::Descend => {
if !collect_dispatch(source, child, dispatch) {
return false;
}
}
NodeAction::Output(exprs) => {
if dispatch.is_some() {
return false;
}
let Some(arms) = boolean_output_arms(source, child, exprs.as_deref()) else {
return false;
};
*dispatch = Some(arms);
}
NodeAction::Assignment(_) | NodeAction::With(_) | NodeAction::Range(_) => {
return false;
}
}
}
true
}
fn boolean_output_arms(
source: &str,
node: tree_sitter::Node<'_>,
exprs: Option<&[TemplateExpr]>,
) -> Option<Vec<LiteralDispatchArm>> {
let [expr] = exprs? else {
return None;
};
if !boolean_valued(expr) {
return None;
}
let raw = node.utf8_text(source.as_bytes()).ok()?;
Some(vec![
LiteralDispatchArm {
header: Some(TemplateHeader::parse_control(raw.trim())),
literal: "true".to_string(),
raw_empty: false,
},
LiteralDispatchArm {
header: None,
literal: "false".to_string(),
raw_empty: false,
},
])
}
fn boolean_valued(expr: &TemplateExpr) -> bool {
use helm_schema_ast::Literal;
match expr.deparen() {
TemplateExpr::Literal(Literal::Bool(_)) => true,
TemplateExpr::Call { function, args } => match function.as_str() {
"eq" | "ne" | "lt" | "le" | "gt" | "ge" | "not" | "hasKey" | "has" | "contains"
| "empty" | "kindIs" | "typeIs" | "regexMatch" | "mustRegexMatch" | "hasPrefix"
| "hasSuffix" => true,
"and" | "or" => !args.is_empty() && args.iter().all(boolean_valued),
_ => false,
},
_ => false,
}
}
fn dispatch_arms(
source: &str,
node: tree_sitter::Node<'_>,
header: Option<TemplateHeader>,
) -> Option<Vec<LiteralDispatchArm>> {
let mut arms = vec![(Some(header?), children_with_field(node, "consequence"))];
for (arm_header, children) in else_if_pairs(node, source) {
arms.push((Some(arm_header?), children));
}
arms.push((None, children_with_field(node, "alternative")));
let mut out = Vec::new();
for (arm_header, children) in arms {
let mut literal = String::new();
for child in children {
if matches!(child.kind(), "{{" | "{{-" | "}}" | "-}}") {
continue;
}
match node_action(source, child) {
NodeAction::Text => {
literal.push_str(child.utf8_text(source.as_bytes()).ok()?);
}
NodeAction::Suppressed => {}
NodeAction::Output(exprs) => {
let [expr] = exprs.as_deref()? else {
return None;
};
literal.push_str(&literal_output_text(expr)?);
}
_ => return None,
}
}
out.push(LiteralDispatchArm {
header: arm_header,
raw_empty: literal.is_empty(),
literal: literal.trim().to_string(),
});
}
Some(out)
}
fn literal_output_text(expr: &helm_schema_ast::TemplateExpr) -> Option<String> {
use helm_schema_ast::{Literal, TemplateExpr};
match expr.deparen() {
TemplateExpr::Literal(Literal::String(text) | Literal::RawString(text)) => {
Some(text.clone())
}
TemplateExpr::Literal(Literal::Bool(value)) => Some(value.to_string()),
TemplateExpr::Literal(Literal::Int(value)) => Some(value.to_string()),
TemplateExpr::Call { function, args } if function == "print" => match args.as_slice() {
[argument] => literal_output_text(argument),
_ => None,
},
_ => None,
}
}