Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix edge case around nested empty partials #658

Merged
merged 3 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 48 additions & 6 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ pub struct RenderContextInner<'reg: 'rc, 'rc> {
/// root template name
root_template: Option<&'reg String>,
disable_escape: bool,
// whether the previous text that we rendered ended on a newline
// necessary to make indenting decisions after the end of partials

// Indicates whether the previous text that we rendered ended on a newline.
// This is necessary to make indenting decisions after the end of partials.
trailing_newline: bool,
// whether the previous text that we render should indent itself

// This should be set to true whenever any output is written.
// We need this to detect empty partials/helpers for indenting decisions.
content_produced: bool,

// The next text that we render should indent itself.
indent_before_write: bool,
indent_string: Option<Cow<'reg, str>>,
}
Expand All @@ -69,6 +75,7 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
root_template,
disable_escape: false,
trailing_newline: false,
content_produced: false,
indent_before_write: false,
indent_string: None,
});
Expand Down Expand Up @@ -305,6 +312,16 @@ impl<'reg: 'rc, 'rc> RenderContext<'reg, 'rc> {
self.inner().trailing_newline
}

#[inline]
pub fn set_content_produced(&mut self, content_produced: bool) {
self.inner_mut().content_produced = content_produced;
}

#[inline]
pub fn get_content_produced(&self) -> bool {
self.inner().content_produced
}

#[inline]
pub fn set_indent_before_write(&mut self, indent_before_write: bool) {
self.inner_mut().indent_before_write = indent_before_write;
Expand Down Expand Up @@ -787,9 +804,21 @@ fn render_helper<'reg: 'rc, 'rc>(
h.hash()
);
let mut call_indent_aware = |helper_def: &dyn HelperDef, rc: &mut RenderContext<'reg, 'rc>| {
rc.set_indent_before_write(ht.indent_before_write && rc.get_trailine_newline());
let indent_directive_before = rc.get_indent_before_write();
let content_produced_before = rc.get_content_produced();
rc.set_content_produced(false);
rc.set_indent_before_write(
indent_directive_before || (ht.indent_before_write && rc.get_trailine_newline()),
);

helper_def.call(&h, registry, ctx, rc, out)?;
rc.set_indent_before_write(rc.get_trailine_newline());

if rc.get_content_produced() {
rc.set_indent_before_write(rc.get_trailine_newline());
} else {
rc.set_content_produced(content_produced_before);
rc.set_indent_before_write(indent_directive_before);
}
Ok(())
};
if let Some(ref d) = rc.get_local_helper(h.name()) {
Expand Down Expand Up @@ -828,6 +857,7 @@ pub fn indent_aware_write(
if v.is_empty() {
return Ok(());
}
rc.set_content_produced(true);

if !v.starts_with(newline_matcher) && rc.get_indent_before_write() {
if let Some(indent) = rc.get_indent_string() {
Expand Down Expand Up @@ -905,11 +935,23 @@ impl Renderable for TemplateElement {
DecoratorExpression(_) | DecoratorBlock(_) => self.eval(registry, ctx, rc),
PartialExpression(ref dt) | PartialBlock(ref dt) => {
let di = Decorator::try_from_template(dt, registry, ctx, rc)?;

let indent_directive_before = rc.get_indent_before_write();
let content_produced_before = rc.get_content_produced();

rc.set_indent_before_write(
dt.indent_before_write && (rc.get_trailine_newline() || dt.indent.is_some()),
);
rc.set_content_produced(false);

partial::expand_partial(&di, registry, ctx, rc, out)?;
rc.set_indent_before_write(rc.get_trailine_newline());

if rc.get_content_produced() {
rc.set_indent_before_write(rc.get_trailine_newline());
} else {
rc.set_content_produced(content_produced_before);
rc.set_indent_before_write(indent_directive_before);
}
Ok(())
}
_ => Ok(()),
Expand Down
30 changes: 30 additions & 0 deletions tests/whitespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,36 @@ foo
);
}

#[test]
fn test_empty_inline_partials_and_helpers_retain_indent_directive() {
let input = r#"
{{~#*inline "empty_partial"}}{{/inline}}

{{~#*inline "indented_partial"}}
{{>empty_partial}}{{#if true}}{{>empty_partial}}{{/if}}foo
{{/inline}}
{{>indented_partial}}
"#;
let output = " foo\n";
let hbs = Handlebars::new();

assert_eq!(hbs.render_template(input, &()).unwrap(), output);
}

#[test]
fn test_indent_directive_propagated_but_not_restored_if_content_was_written() {
let input = r#"
{{~#*inline "indented_partial"}}
{{#if true}}{{/if}}{{#if true}}foo{{/if}}foo
{{/inline}}
{{>indented_partial}}
"#;
let output = " foofoo\n";
let hbs = Handlebars::new();

assert_eq!(hbs.render_template(input, &()).unwrap(), output);
}

//regression test for #611
#[test]
fn tag_before_eof_becomes_standalone_in_full_template() {
Expand Down
Loading