Repository: martinohmann/hcl-rs (rust)

Issue:
incorrect handling of escaped template interpolations and directives
The following does not fail to parse, but fails the assertion because `$${escaped2}` is split into a `$` literal and the interpolation `${escaped2}`:

```rust
let input = "$${escaped1} ${unescaped} $${escaped2} $$ESCAPED_SHELL_VAR\n$SHELL_VAR";
let parsed: Template = input.parse().unwrap();
let expected = Template::from_iter([
    Element::from("${escaped1} "),
    Element::from(Interpolation::new(Ident::new("unescaped"))),
    Element::from(" ${escaped2} $$ESCAPED_SHELL_VAR\n$SHELL_VAR"),
]);

assert_eq!(parsed, expected);
```

This is a parser bug.

Pull request:
fix(parser): correctly handle escaped markers in templates
Fixes #256

Patch:
diff --git a/crates/hcl-edit/src/parser/string.rs b/crates/hcl-edit/src/parser/string.rs
index 9afd4af..292be50 100644
--- a/crates/hcl-edit/src/parser/string.rs
+++ b/crates/hcl-edit/src/parser/string.rs
@@ -119,9 +119,12 @@ fn any_until<'a, F, T>(literal_end: F) -> impl Parser<Input<'a>, &'a str, ParseE
 where
     F: Parser<Input<'a>, T, ParseError<Input<'a>>>,
 {
-    void(repeat(1.., preceded(not(literal_end), any)))
-        .recognize()
-        .try_map(std::str::from_utf8)
+    void(repeat(
+        1..,
+        preceded(not(alt((escaped_marker.void(), literal_end.void()))), any),
+    ))
+    .recognize()
+    .try_map(std::str::from_utf8)
 }
 
 /// Parse an escaped start marker for a template interpolation or directive.


Test patch:
diff --git a/crates/hcl-edit/src/parser/tests.rs b/crates/hcl-edit/src/parser/tests.rs
index 7fd5f8d..2b2d139 100644
--- a/crates/hcl-edit/src/parser/tests.rs
+++ b/crates/hcl-edit/src/parser/tests.rs
@@ -2,11 +2,7 @@ use super::expr::expr;
 use super::parse_complete;
 use super::structure::body;
 use super::template::template;
-use crate::{
-    expr::Expression,
-    template::{Element, Interpolation, StringTemplate},
-    Formatted, Ident, Number,
-};
+use crate::{expr::Expression, Formatted, Number};
 use indoc::indoc;
 use pretty_assertions::assert_eq;
 
@@ -25,17 +21,6 @@ fn number_expr() {
     assert_eq!(parsed, expected);
 }
 
-#[test]
-fn escaped_string_template() {
-    let parsed = parse_complete(r#""$${escaped} ${unescaped}""#, expr).unwrap();
-    let template = StringTemplate::from_iter([
-        Element::Literal(String::from("${escaped} ").into()),
-        Element::Interpolation(Interpolation::new(Ident::new("unescaped"))),
-    ]);
-    let expected = Expression::Template(template);
-    assert_eq!(parsed, expected);
-}
-
 #[test]
 fn roundtrip_expr() {
     let inputs = [
diff --git a/crates/hcl-edit/tests/regressions.rs b/crates/hcl-edit/tests/regressions.rs
index c02bdd5..8c397f3 100644
--- a/crates/hcl-edit/tests/regressions.rs
+++ b/crates/hcl-edit/tests/regressions.rs
@@ -1,4 +1,7 @@
 use hcl_edit::expr::Expression;
+use hcl_edit::template::{Element, Interpolation, Template};
+use hcl_edit::Ident;
+use pretty_assertions::assert_eq;
 
 // https://github.com/martinohmann/hcl-rs/issues/248
 #[test]
@@ -11,3 +14,17 @@ fn issue_248() {
     let parsed: Expression = encoded.parse().unwrap();
     assert_eq!(parsed, expr);
 }
+
+// https://github.com/martinohmann/hcl-rs/issues/256
+#[test]
+fn issue_256() {
+    let input = "$${escaped1} ${unescaped} $${escaped2} $$ESCAPED_SHELL_VAR\n$SHELL_VAR";
+    let parsed: Template = input.parse().unwrap();
+    let expected = Template::from_iter([
+        Element::from("${escaped1} "),
+        Element::from(Interpolation::new(Ident::new("unescaped"))),
+        Element::from(" ${escaped2} $$ESCAPED_SHELL_VAR\n$SHELL_VAR"),
+    ]);
+
+    assert_eq!(parsed, expected);
+}