Repository: mitsuhiko/minijinja (rust)

Issue:
Syntax error on import with `with context` qualifier
## Description

I'm trying to make a Jinja2 code base compatible with minijinja and got almost everything working. What doesn't work is the `with context` qualifier in imports, which as I understand is automatically applied for minijinja. However, to keep our templates compatible, we're currently just stripping `with context` from all files. @mitsuhiko  and I had a short conversation on Twitter where it was stated that this should be supported, but 2.8.0 still errors.

```
-------------------------------- template.html --------------------------------
   1 | <!doctype html>
   2 | <title>Demo</title>
   3 > {%- import "macros.html" as all_macros with context %}
     i                                        ^^^^ syntax error
   4 | {%- from "macros.html" import render_input %}
   5 | <form action="" method="post">
   6 |   <dl>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No referenced variables
-------------------------------------------------------------------------------
```

## Reproduction steps

1. Run [reproduction.zip](https://github.com/user-attachments/files/19508282/reproduction.zip) I built from the macros example

Additional helpful information:

- Version of minijinja: 2.8.0
- Version of rustc: 1.85.0
- Operating system and version: macOS Ventura

## What did you expect

No error 🎉




Pull request:
Correctly handle with/without context for imports
The context markers were not implemented for `{% import %}` and `{% from ... import %}`.

Fixes #758

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bb5a8e..47bee62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,8 @@ All notable changes to MiniJinja are documented here.
   freed.  #754
 - Fix a compilation issue on 32bit systems when `AtomicU64` is
   not available in minijinja-contrib.  #755
+- Correctly handle `with context` and `without context` for
+  imports.  #759
 
 ## 2.8.0
 
diff --git a/minijinja/src/compiler/parser.rs b/minijinja/src/compiler/parser.rs
index 4ed2426..d7a04b4 100644
--- a/minijinja/src/compiler/parser.rs
+++ b/minijinja/src/compiler/parser.rs
@@ -1068,17 +1068,12 @@ impl<'a> Parser<'a> {
     #[cfg(feature = "multi_template")]
     fn parse_include(&mut self) -> Result<ast::Include<'a>, Error> {
         let name = ok!(self.parse_expr());
-
-        // with/without context is without meaning in MiniJinja, but for syntax
-        // compatibility it's supported.
-        if skip_token!(self, Token::Ident("without" | "with")) {
-            expect_token!(self, Token::Ident("context"), "missing keyword");
-        }
+        let skipped_context = ok!(self.skip_context_marker());
 
         let ignore_missing = if skip_token!(self, Token::Ident("ignore")) {
             expect_token!(self, Token::Ident("missing"), "missing keyword");
-            if skip_token!(self, Token::Ident("without" | "with")) {
-                expect_token!(self, Token::Ident("context"), "missing keyword");
+            if !skipped_context {
+                ok!(self.skip_context_marker());
             }
             true
         } else {
@@ -1095,6 +1090,7 @@ impl<'a> Parser<'a> {
         let expr = ok!(self.parse_expr());
         expect_token!(self, Token::Ident("as"), "as");
         let name = ok!(self.parse_expr());
+        ok!(self.skip_context_marker());
         Ok(ast::Import { expr, name })
     }
 
@@ -1104,13 +1100,13 @@ impl<'a> Parser<'a> {
         let mut names = Vec::new();
         expect_token!(self, Token::Ident("import"), "import");
         loop {
-            if matches_token!(self, Token::BlockEnd) {
+            if ok!(self.skip_context_marker()) || matches_token!(self, Token::BlockEnd) {
                 break;
             }
             if !names.is_empty() {
                 expect_token!(self, Token::Comma, "`,`");
             }
-            if matches_token!(self, Token::BlockEnd) {
+            if ok!(self.skip_context_marker()) || matches_token!(self, Token::BlockEnd) {
                 break;
             }
             let name = ok!(self.parse_assign_name(false));
@@ -1124,6 +1120,18 @@ impl<'a> Parser<'a> {
         Ok(ast::FromImport { expr, names })
     }
 
+    #[cfg(feature = "multi_template")]
+    fn skip_context_marker(&mut self) -> Result<bool, Error> {
+        // with/without context is without meaning in MiniJinja, but for syntax
+        // copatibility it's supported.
+        if skip_token!(self, Token::Ident("with") | Token::Ident("without")) {
+            expect_token!(self, Token::Ident("context"), "context");
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
     #[cfg(feature = "macros")]
     fn parse_macro_args_and_defaults(
         &mut self,


Test patch:
diff --git a/minijinja/tests/parser-inputs/imports.txt b/minijinja/tests/parser-inputs/imports.txt
index 7eb28d1..c7be9bf 100644
--- a/minijinja/tests/parser-inputs/imports.txt
+++ b/minijinja/tests/parser-inputs/imports.txt
@@ -6,4 +6,10 @@
 {% from "foo.html" import a, %}
 {% from "foo.html" import a as b, %}
 {% from "foo.html" import a as b, b as c, %}
+{% from "foo.html" import a as b, b as c with context %}
+{% from "foo.html" import a as b, b as c without context %}
+{% from "foo.html" import a as b, b as c, with context %}
+{% from "foo.html" import a as b, b as c, without context %}
 {% import "foo.html" as x %}
+{% import "foo.html" as x with context %}
+{% import "foo.html" as x without context %}
diff --git a/minijinja/tests/snapshots/test_parser__parser@imports.txt.snap b/minijinja/tests/snapshots/test_parser__parser@imports.txt.snap
index e395040..5b34062 100644
--- a/minijinja/tests/snapshots/test_parser__parser@imports.txt.snap
+++ b/minijinja/tests/snapshots/test_parser__parser@imports.txt.snap
@@ -1,6 +1,6 @@
 ---
 source: minijinja/tests/test_parser.rs
-description: "{% from \"foo.html\" import a, b %}\n{% from \"foo.html\" import a %}\n{% from \"foo.html\" import a as b %}\n{% from \"foo.html\" import a as b, b as c %}\n{% from \"foo.html\" import a, b, %}\n{% from \"foo.html\" import a, %}\n{% from \"foo.html\" import a as b, %}\n{% from \"foo.html\" import a as b, b as c, %}\n{% import \"foo.html\" as x %}"
+description: "{% from \"foo.html\" import a, b %}\n{% from \"foo.html\" import a %}\n{% from \"foo.html\" import a as b %}\n{% from \"foo.html\" import a as b, b as c %}\n{% from \"foo.html\" import a, b, %}\n{% from \"foo.html\" import a, %}\n{% from \"foo.html\" import a as b, %}\n{% from \"foo.html\" import a as b, b as c, %}\n{% from \"foo.html\" import a as b, b as c with context %}\n{% from \"foo.html\" import a as b, b as c without context %}\n{% from \"foo.html\" import a as b, b as c, with context %}\n{% from \"foo.html\" import a as b, b as c, without context %}\n{% import \"foo.html\" as x %}\n{% import \"foo.html\" as x with context %}\n{% import \"foo.html\" as x without context %}"
 input_file: minijinja/tests/parser-inputs/imports.txt
 ---
 Ok(
@@ -182,14 +182,156 @@ Ok(
             EmitRaw {
                 raw: "\n",
             } @ 8:44-9:0,
+            FromImport {
+                expr: Const {
+                    value: "foo.html",
+                } @ 9:8-9:18,
+                names: [
+                    (
+                        Var {
+                            id: "a",
+                        } @ 9:26-9:27,
+                        Some(
+                            Var {
+                                id: "b",
+                            } @ 9:31-9:32,
+                        ),
+                    ),
+                    (
+                        Var {
+                            id: "b",
+                        } @ 9:34-9:35,
+                        Some(
+                            Var {
+                                id: "c",
+                            } @ 9:39-9:40,
+                        ),
+                    ),
+                ],
+            } @ 9:3-9:53,
+            EmitRaw {
+                raw: "\n",
+            } @ 9:56-10:0,
+            FromImport {
+                expr: Const {
+                    value: "foo.html",
+                } @ 10:8-10:18,
+                names: [
+                    (
+                        Var {
+                            id: "a",
+                        } @ 10:26-10:27,
+                        Some(
+                            Var {
+                                id: "b",
+                            } @ 10:31-10:32,
+                        ),
+                    ),
+                    (
+                        Var {
+                            id: "b",
+                        } @ 10:34-10:35,
+                        Some(
+                            Var {
+                                id: "c",
+                            } @ 10:39-10:40,
+                        ),
+                    ),
+                ],
+            } @ 10:3-10:56,
+            EmitRaw {
+                raw: "\n",
+            } @ 10:59-11:0,
+            FromImport {
+                expr: Const {
+                    value: "foo.html",
+                } @ 11:8-11:18,
+                names: [
+                    (
+                        Var {
+                            id: "a",
+                        } @ 11:26-11:27,
+                        Some(
+                            Var {
+                                id: "b",
+                            } @ 11:31-11:32,
+                        ),
+                    ),
+                    (
+                        Var {
+                            id: "b",
+                        } @ 11:34-11:35,
+                        Some(
+                            Var {
+                                id: "c",
+                            } @ 11:39-11:40,
+                        ),
+                    ),
+                ],
+            } @ 11:3-11:54,
+            EmitRaw {
+                raw: "\n",
+            } @ 11:57-12:0,
+            FromImport {
+                expr: Const {
+                    value: "foo.html",
+                } @ 12:8-12:18,
+                names: [
+                    (
+                        Var {
+                            id: "a",
+                        } @ 12:26-12:27,
+                        Some(
+                            Var {
+                                id: "b",
+                            } @ 12:31-12:32,
+                        ),
+                    ),
+                    (
+                        Var {
+                            id: "b",
+                        } @ 12:34-12:35,
+                        Some(
+                            Var {
+                                id: "c",
+                            } @ 12:39-12:40,
+                        ),
+                    ),
+                ],
+            } @ 12:3-12:57,
+            EmitRaw {
+                raw: "\n",
+            } @ 12:60-13:0,
+            Import {
+                expr: Const {
+                    value: "foo.html",
+                } @ 13:10-13:20,
+                name: Var {
+                    id: "x",
+                } @ 13:24-13:25,
+            } @ 13:3-13:25,
+            EmitRaw {
+                raw: "\n",
+            } @ 13:28-14:0,
+            Import {
+                expr: Const {
+                    value: "foo.html",
+                } @ 14:10-14:20,
+                name: Var {
+                    id: "x",
+                } @ 14:24-14:25,
+            } @ 14:3-14:38,
+            EmitRaw {
+                raw: "\n",
+            } @ 14:41-15:0,
             Import {
                 expr: Const {
                     value: "foo.html",
-                } @ 9:10-9:20,
+                } @ 15:10-15:20,
                 name: Var {
                     id: "x",
-                } @ 9:24-9:25,
-            } @ 9:3-9:25,
+                } @ 15:24-15:25,
+            } @ 15:3-15:41,
         ],
-    } @ 0:0-9:28,
+    } @ 0:0-15:44,
 )