use crate::span::Span;
#[derive(Debug, Clone)]
pub struct StackFrame {
pub description: String,
pub template_name: String,
pub line: u32,
pub col: u32,
pub in_macro: Option<String>,
pub nesting: bool,
}
impl StackFrame {
pub fn location(&self) -> String {
let mut s = format!("[in template \"{}\"", self.template_name);
if let Some(m) = &self.in_macro {
s.push_str(&format!(" in macro \"{m}\""));
}
s.push_str(&format!(" at line {}, column {}]", self.line, self.col));
s
}
}
#[derive(Debug, Clone, Default)]
pub struct ErrorCtx {
pub template_name: Option<String>,
pub span: Span,
pub instruction_stack: Vec<StackFrame>,
pub blamer: Option<String>,
pub blamed_expr: Option<String>,
pub assignment_target: Option<String>,
pub expected_phrase: Option<String>,
pub extra_tip: Option<String>,
}
impl ErrorCtx {
pub fn blamed_location(&self) -> String {
match &self.template_name {
Some(t) if self.span.line > 0 => {
format!(
" [in template \"{t}\" at line {}, column {}]",
self.span.line, self.span.col
)
}
_ => String::new(),
}
}
}
pub fn render_ftl_stack_section(stack: &[StackFrame]) -> Option<String> {
if stack.is_empty() {
return None;
}
let mut lines = Vec::with_capacity(stack.len());
let mut prev_nesting = false;
for (idx, frame) in stack.iter().enumerate() {
let marker = if idx == 0 {
"- Failed at: "
} else {
let nesting = frame.nesting || prev_nesting;
if nesting {
"~ Reached through: "
} else {
"- Reached through: "
}
};
prev_nesting = frame.nesting;
lines.push(format!(
"\t{marker}{} {}",
frame.description,
frame.location()
));
}
Some(format!(
"\n\n----\nFTL stack trace (\"~\" means nesting-related):\n{}\n----",
lines.join("\n")
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stack_section_format() {
let stack = vec![
StackFrame {
description: "${missing}".to_string(),
template_name: "t.ftl".to_string(),
line: 1,
col: 11,
in_macro: Some("m".to_string()),
nesting: false,
},
StackFrame {
description: "@m".to_string(),
template_name: "t.ftl".to_string(),
line: 1,
col: 30,
in_macro: None,
nesting: false,
},
];
let s = render_ftl_stack_section(&stack).unwrap();
assert_eq!(
s,
"\n\n----\nFTL stack trace (\"~\" means nesting-related):\n\
\t- Failed at: ${missing} [in template \"t.ftl\" in macro \"m\" at line 1, column 11]\n\
\t- Reached through: @m [in template \"t.ftl\" at line 1, column 30]\n----"
);
}
#[test]
fn nesting_markers() {
let stack = vec![
StackFrame {
description: "${missing}".to_string(),
template_name: "t.ftl".to_string(),
line: 1,
col: 33,
in_macro: None,
nesting: false,
},
StackFrame {
description: "#nested".to_string(),
template_name: "t.ftl".to_string(),
line: 1,
col: 11,
in_macro: Some("m".to_string()),
nesting: true,
},
StackFrame {
description: "@m".to_string(),
template_name: "t.ftl".to_string(),
line: 1,
col: 29,
in_macro: None,
nesting: false,
},
];
let s = render_ftl_stack_section(&stack).unwrap();
assert_eq!(
s,
"\n\n----\nFTL stack trace (\"~\" means nesting-related):\n\
\t- Failed at: ${missing} [in template \"t.ftl\" at line 1, column 33]\n\
\t~ Reached through: #nested [in template \"t.ftl\" in macro \"m\" at line 1, column 11]\n\
\t~ Reached through: @m [in template \"t.ftl\" at line 1, column 29]\n----"
);
}
}