Repository: mitsuhiko/minijinja (language: rust)

Issue:
String concatenation without "+" operator
Jinja allowed Python strings as `"abc" "def"` for concatenation at "compile time". For compatibility, it would be helpful if minijinja did as well.


Discussion / hints:
Oh well. I completely forgot that this is a thing. Apparently there since 2008. https://github.com/pallets/jinja/commit/4778bda8312875a943e3f16df3efc5f6ffb1e586

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8fdfd5e6..2dae9a17 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 All notable changes to MiniJinja are documented here.
 
+## 1.0.20
+
+- Added support for implicit string concatenation for Jinja2 compatibility.  #489
+
 ## 1.0.19
 
 - Deprecated `Value::from_iterator` and introduced replacement
diff --git a/minijinja/src/compiler/parser.rs b/minijinja/src/compiler/parser.rs
index 4236cf56..c6f6720c 100644
--- a/minijinja/src/compiler/parser.rs
+++ b/minijinja/src/compiler/parser.rs
@@ -534,7 +534,7 @@ impl<'a> Parser<'a> {
         let (token, span) = expect_token!(self, "expression");
         macro_rules! const_val {
             ($expr:expr) => {
-                make_const(Value::from($expr), span)
+                make_const(Value::from($expr), self.stream.expand_span(span))
             };
         }
 
@@ -543,7 +543,18 @@ impl<'a> Parser<'a> {
             Token::Ident("false" | "False") => Ok(const_val!(false)),
             Token::Ident("none" | "None") => Ok(const_val!(())),
             Token::Ident(name) => Ok(ast::Expr::Var(Spanned::new(ast::Var { id: name }, span))),
-            Token::Str(val) => Ok(const_val!(val)),
+            Token::Str(val) => {
+                if matches_token!(self, Token::Str(_)) {
+                    let mut buf = String::from(val);
+                    while let Some((Token::Str(s), _)) = ok!(self.stream.current()) {
+                        buf.push_str(s);
+                        ok!(self.stream.next());
+                    }
+                    Ok(const_val!(buf))
+                } else {
+                    Ok(const_val!(val))
+                }
+            }
             Token::String(val) => Ok(const_val!(val)),
             Token::Int(val) => Ok(const_val!(val)),
             Token::Int128(val) => Ok(const_val!(val)),


Test patch:
diff --git a/minijinja/tests/parser-inputs/string-implicit-concat.txt b/minijinja/tests/parser-inputs/string-implicit-concat.txt
new file mode 100644
index 00000000..a0c6285a
--- /dev/null
+++ b/minijinja/tests/parser-inputs/string-implicit-concat.txt
@@ -0,0 +1,3 @@
+{{ "foo" }}
+{{ "foo" "bar" }}
+{{ "foo" "bar" "baz" }}
diff --git a/minijinja/tests/snapshots/test_parser__parser@string-implicit-concat.txt.snap b/minijinja/tests/snapshots/test_parser__parser@string-implicit-concat.txt.snap
new file mode 100644
index 00000000..231267f9
--- /dev/null
+++ b/minijinja/tests/snapshots/test_parser__parser@string-implicit-concat.txt.snap
@@ -0,0 +1,32 @@
+---
+source: minijinja/tests/test_parser.rs
+description: "{{ \"foo\" }}\n{{ \"foo\" \"bar\" }}\n{{ \"foo\" \"bar\" \"baz\" }}"
+input_file: minijinja/tests/parser-inputs/string-implicit-concat.txt
+---
+Ok(
+    Template {
+        children: [
+            EmitExpr {
+                expr: Const {
+                    value: "foo",
+                } @ 1:3-1:8,
+            } @ 1:0-1:8,
+            EmitRaw {
+                raw: "\n",
+            } @ 1:11-2:0,
+            EmitExpr {
+                expr: Const {
+                    value: "foobar",
+                } @ 2:3-2:14,
+            } @ 2:0-2:14,
+            EmitRaw {
+                raw: "\n",
+            } @ 2:17-3:0,
+            EmitExpr {
+                expr: Const {
+                    value: "foobarbaz",
+                } @ 3:3-3:20,
+            } @ 3:0-3:20,
+        ],
+    } @ 0:0-3:23,
+)