Repository: sunng87/handlebars-rust (rust)

Issue:
Extra whitespace added to `each` when the `/each` isn't followed by `\n`
We noticed this issues when upgrading to `4.x`. If an `/each` is not followed by a `\n` the rendered template will contain extra whitespace. This throws off formats which expect consistent whitespace.

Here is a repro:

```rust
#[test]
    fn no_newline_for_each() {
        let reg = Registry::new();

        let tpl = r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}"#;
        assert_eq!(
            r#"<ul>
    <li>0</li>
    <li>1</li>
    <li>2</li>"#,
            reg.render_template(tpl, &json!({"a": [0, 1, 2]})).unwrap()
        );
    }
```

Using the template:

```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}"#
```
Results in:

```
---- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n      <li>1</li>\n      <li>2</li>\n  "`', src/helpers/helper_each.rs:620:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

Using the template:

```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}aa"#
```
Results in:
```
---- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n      <li>1</li>\n      <li>2</li>\n  aa"`', src/helpers/helper_each.rs:620:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
Using the template:
```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}a
"#;
```
Results in:
```
-- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n      <li>1</li>\n      <li>2</li>\n  a\n"`', src/helpers/helper_each.rs:621:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
Using the template:
```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}
"#
```
Results in:
```
---- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>\n"`', src/helpers/helper_each.rs:621:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

As long as the `/each` is followed by a `\n` we get predictable whitespace.
Extra whitespace added to `each` when the `/each` isn't followed by `\n`
We noticed this issues when upgrading to `4.x`. If an `/each` is not followed by a `\n` the rendered template will contain extra whitespace. This throws off formats which expect consistent whitespace.

Here is a repro:

```rust
#[test]
    fn no_newline_for_each() {
        let reg = Registry::new();

        let tpl = r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}"#;
        assert_eq!(
            r#"<ul>
    <li>0</li>
    <li>1</li>
    <li>2</li>"#,
            reg.render_template(tpl, &json!({"a": [0, 1, 2]})).unwrap()
        );
    }
```

Using the template:

```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}"#
```
Results in:

```
---- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n      <li>1</li>\n      <li>2</li>\n  "`', src/helpers/helper_each.rs:620:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

Using the template:

```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}aa"#
```
Results in:
```
---- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n      <li>1</li>\n      <li>2</li>\n  aa"`', src/helpers/helper_each.rs:620:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
Using the template:
```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}a
"#;
```
Results in:
```
-- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n      <li>1</li>\n      <li>2</li>\n  a\n"`', src/helpers/helper_each.rs:621:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
Using the template:
```
r#"<ul>
  {{#each a}}
    {{!-- comment --}}
    <li>{{this}}</li>
  {{/each}}
"#
```
Results in:
```
---- helpers::helper_each::test::no_newline_for_each stdout ----
thread 'helpers::helper_each::test::no_newline_for_each' panicked at 'assertion failed: `(left == right)`
  left: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>"`,
 right: `"<ul>\n    <li>0</li>\n    <li>1</li>\n    <li>2</li>\n"`', src/helpers/helper_each.rs:621:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

As long as the `/each` is followed by a `\n` we get predictable whitespace.


Discussion:
Thank you for reporting. I can reproduce this issue with 4.x and 5.0 beta.

links for comparing with javascript version:

- https://sunng87.github.io/handlebars-rust/?tpl=%3Cul%3E%0A%20%20%7B%7B%23each%20a%7D%7D%0A%20%20%20%20%3Cli%3E%7B%7Bthis%7D%7D%3C%2Fli%3E%0A%20%20%7B%7B%2Feach%7D%7D&data=%7B%22a%22%3A%20%5B1%2C%202%5D%7D
- https://sunng87.github.io/handlebars-rust/?tpl=%3Cul%3E%0A%20%20%7B%7B%23each%20a%7D%7D%0A%20%20%20%20%3Cli%3E%7B%7Bthis%7D%7D%3C%2Fli%3E%0A%20%20%7B%7B%2Feach%7D%7Da&data=%7B%22a%22%3A%20%5B1%2C%202%5D%7D
Thank you for reporting. I can reproduce this issue with 4.x and 5.0 beta.

links for comparing with javascript version:

- https://sunng87.github.io/handlebars-rust/?tpl=%3Cul%3E%0A%20%20%7B%7B%23each%20a%7D%7D%0A%20%20%20%20%3Cli%3E%7B%7Bthis%7D%7D%3C%2Fli%3E%0A%20%20%7B%7B%2Feach%7D%7D&data=%7B%22a%22%3A%20%5B1%2C%202%5D%7D
- https://sunng87.github.io/handlebars-rust/?tpl=%3Cul%3E%0A%20%20%7B%7B%23each%20a%7D%7D%0A%20%20%20%20%3Cli%3E%7B%7Bthis%7D%7D%3C%2Fli%3E%0A%20%20%7B%7B%2Feach%7D%7Da&data=%7B%22a%22%3A%20%5B1%2C%202%5D%7D

Patch:
diff --git a/src/registry.rs b/src/registry.rs
--- a/src/registry.rs
+++ b/src/registry.rs
@@ -272,6 +272,7 @@ impl<'reg> Registry<'reg> {
             tpl_str.as_ref(),
             TemplateOptions {
                 name: Some(name.to_owned()),
+                is_partial: false,
                 prevent_indent: self.prevent_indent,
             },
         )?;
@@ -593,6 +594,7 @@ impl<'reg> Registry<'reg> {
                         TemplateOptions {
                             name: Some(name.to_owned()),
                             prevent_indent: self.prevent_indent,
+                            is_partial: false,
                         },
                     )
                 })
diff --git a/src/template.rs b/src/template.rs
--- a/src/template.rs
+++ b/src/template.rs
@@ -30,6 +30,7 @@ pub struct Template {
 #[derive(Default)]
 pub(crate) struct TemplateOptions {
     pub(crate) prevent_indent: bool,
+    pub(crate) is_partial: bool,
     pub(crate) name: Option<String>,
 }
 
@@ -570,9 +571,15 @@ impl Template {
         source: &str,
         current_span: &Span<'_>,
         prevent_indent: bool,
+        is_partial: bool,
     ) -> bool {
-        let with_trailing_newline =
-            support::str::starts_with_empty_line(&source[current_span.end()..]);
+        let continuation = &source[current_span.end()..];
+
+        let mut with_trailing_newline = support::str::starts_with_empty_line(continuation);
+
+        // For full templates, we behave as if there was a trailing newline if we encounter
+        // the end of input. See #611.
+        with_trailing_newline |= !is_partial && continuation.is_empty();
 
         if with_trailing_newline {
             let with_leading_newline =
@@ -770,6 +777,7 @@ impl Template {
                             source,
                             &span,
                             true,
+                            options.is_partial,
                         );
 
                         let indent_before_write = trim_line_required && !exp.omit_pre_ws;
@@ -812,6 +820,7 @@ impl Template {
                             source,
                             &span,
                             true,
+                            options.is_partial,
                         );
 
                         let indent_before_write = trim_line_required && !exp.omit_pre_ws;
@@ -884,6 +893,7 @@ impl Template {
                                     source,
                                     &span,
                                     prevent_indent,
+                                    options.is_partial,
                                 );
 
                                 // indent for partial expression >
@@ -919,6 +929,7 @@ impl Template {
                                     source,
                                     &span,
                                     true,
+                                    options.is_partial,
                                 );
 
                                 let mut h = helper_stack.pop_front().unwrap();
@@ -948,6 +959,7 @@ impl Template {
                                     source,
                                     &span,
                                     true,
+                                    options.is_partial,
                                 );
 
                                 let mut d = decorator_stack.pop_front().unwrap();
@@ -981,6 +993,7 @@ impl Template {
                             source,
                             &span,
                             true,
+                            options.is_partial,
                         );
 
                         let text = span
@@ -996,6 +1009,7 @@ impl Template {
                             source,
                             &span,
                             true,
+                            options.is_partial,
                         );
 
                         let text = span


Test patch:
diff --git a/tests/whitespace.rs b/tests/whitespace.rs
--- a/tests/whitespace.rs
+++ b/tests/whitespace.rs
@@ -270,3 +270,49 @@ foo
         output
     );
 }
+
+//regression test for #611
+#[test]
+fn tag_before_eof_becomes_standalone_in_full_template() {
+    let input = r#"<ul>
+  {{#each a}}
+    {{!-- comment --}}
+    <li>{{this}}</li>
+  {{/each}}"#;
+    let output = r#"<ul>
+    <li>1</li>
+    <li>2</li>
+    <li>3</li>
+"#;
+    let hbs = Handlebars::new();
+
+    assert_eq!(
+        hbs.render_template(input, &json!({"a": [1, 2, 3]}))
+            .unwrap(),
+        output
+    );
+}
+
+#[test]
+fn tag_before_eof_does_not_become_standalone_in_partial() {
+    let input = r#"{{#*inline "partial"}}
+<ul>
+  {{#each a}}
+    <li>{{this}}</li>
+  {{/each}}{{/inline}}
+{{> partial}}"#;
+
+    let output = r#"
+<ul>
+    <li>1</li>
+      <li>2</li>
+      <li>3</li>
+  "#;
+    let hbs = Handlebars::new();
+
+    assert_eq!(
+        hbs.render_template(input, &json!({"a": [1, 2, 3]}))
+            .unwrap(),
+        output
+    );
+}