diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerContinueTag.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerContinueTag.java new file mode 100644 index 000000000..1aaef5e43 --- /dev/null +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerContinueTag.java @@ -0,0 +1,33 @@ +package com.hubspot.jinjava.lib.tag.eager; + +import com.google.common.annotations.Beta; +import com.hubspot.jinjava.interpret.DeferredValue; +import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.lib.tag.ContinueTag; +import com.hubspot.jinjava.lib.tag.ForTag; +import com.hubspot.jinjava.tree.parse.TagToken; + +/** + * Eager decorator for the continue tag that handles reconstruction when the continue + * is inside a deferred context (e.g., when in deferred execution mode such as + * inside a deferred if condition within a for loop). + */ +@Beta +public class EagerContinueTag extends EagerTagDecorator { + + public EagerContinueTag() { + super(new ContinueTag()); + } + + public EagerContinueTag(ContinueTag continueTag) { + super(continueTag); + } + + @Override + public String getEagerTagImage(TagToken tagToken, JinjavaInterpreter interpreter) { + if (!(interpreter.getContext().get(ForTag.LOOP) instanceof DeferredValue)) { + interpreter.getContext().replace(ForTag.LOOP, DeferredValue.instance()); + } + return super.getEagerTagImage(tagToken, interpreter); + } +} diff --git a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java index ba3788404..af6b354ae 100644 --- a/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java +++ b/src/main/java/com/hubspot/jinjava/lib/tag/eager/EagerTagFactory.java @@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.hubspot.jinjava.lib.tag.BlockTag; -import com.hubspot.jinjava.lib.tag.BreakTag; import com.hubspot.jinjava.lib.tag.CallTag; import com.hubspot.jinjava.lib.tag.ContinueTag; import com.hubspot.jinjava.lib.tag.CycleTag; @@ -46,6 +45,7 @@ public class EagerTagFactory { .put(IfTag.class, EagerIfTag.class) .put(UnlessTag.class, EagerUnlessTag.class) .put(CallTag.class, EagerCallTag.class) + .put(ContinueTag.class, EagerContinueTag.class) .build(); // These classes don't need an eager decorator. public static final Set> TAG_CLASSES_TO_SKIP = ImmutableSet @@ -56,8 +56,6 @@ public class EagerTagFactory { .add(ElseTag.class) .add(RawTag.class) .add(ExtendsTag.class) // TODO support reconstructing extends tags - .add(BreakTag.class) // TODO support eager break tag - .add(ContinueTag.class) // TODO support eager continue tag .build(); @SuppressWarnings("unchecked") diff --git a/src/test/java/com/hubspot/jinjava/EagerTest.java b/src/test/java/com/hubspot/jinjava/EagerTest.java index e9581f736..e720a02bc 100644 --- a/src/test/java/com/hubspot/jinjava/EagerTest.java +++ b/src/test/java/com/hubspot/jinjava/EagerTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -1647,42 +1648,74 @@ public void itWrapsMacroThatWouldChangeCurrentPathInChildScope() { @Test public void itHandlesDeferredBreakInForLoop() { - String input = expectedTemplateInterpreter.getFixtureTemplate( + expectedTemplateInterpreter.assertExpectedOutput( "handles-deferred-break-in-for-loop/test" ); - interpreter.render(input); - // We don't support this yet - assertThat(interpreter.getContext().getDeferredNodes()).isNotEmpty(); + } + + @Test + public void itHandlesDeferredBreakInForLoopSecondPass() { + localContext.put("deferred", 1); + expectedTemplateInterpreter.assertExpectedOutput( + "handles-deferred-break-in-for-loop/test.expected" + ); + expectedTemplateInterpreter.assertExpectedNonEagerOutput( + "handles-deferred-break-in-for-loop/test.expected" + ); } @Test public void itHandlesBreakInDeferredForLoop() { - String input = expectedTemplateInterpreter.getFixtureTemplate( + expectedTemplateInterpreter.assertExpectedOutput( "handles-break-in-deferred-for-loop/test" ); - interpreter.render(input); - // We don't support this yet - assertThat(interpreter.getContext().getDeferredNodes()).isNotEmpty(); + } + + @Test + public void itHandlesBreakInDeferredForLoopSecondPass() { + localContext.put("deferred", List.of(0, 1, 2, 3, 4, 5)); + expectedTemplateInterpreter.assertExpectedOutput( + "handles-break-in-deferred-for-loop/test.expected" + ); + expectedTemplateInterpreter.assertExpectedNonEagerOutput( + "handles-break-in-deferred-for-loop/test.expected" + ); } @Test public void itHandlesDeferredContinueInForLoop() { - String input = expectedTemplateInterpreter.getFixtureTemplate( + expectedTemplateInterpreter.assertExpectedOutput( "handles-deferred-continue-in-for-loop/test" ); - interpreter.render(input); - // We don't support this yet - assertThat(interpreter.getContext().getDeferredNodes()).isNotEmpty(); + } + + @Test + public void itHandlesDeferredContinueInForLoopSecondPass() { + localContext.put("deferred", 2); + expectedTemplateInterpreter.assertExpectedOutput( + "handles-deferred-continue-in-for-loop/test.expected" + ); + expectedTemplateInterpreter.assertExpectedNonEagerOutput( + "handles-deferred-continue-in-for-loop/test.expected" + ); } @Test public void itHandlesContinueInDeferredForLoop() { - String input = expectedTemplateInterpreter.getFixtureTemplate( + expectedTemplateInterpreter.assertExpectedOutput( "handles-continue-in-deferred-for-loop/test" ); - interpreter.render(input); - // We don't support this yet - assertThat(interpreter.getContext().getDeferredNodes()).isNotEmpty(); + } + + @Test + public void itHandlesContinueInDeferredForLoopSecondPass() { + localContext.put("deferred", List.of(0, 1, 2, 3, 4, 5)); + expectedTemplateInterpreter.assertExpectedOutput( + "handles-continue-in-deferred-for-loop/test.expected" + ); + expectedTemplateInterpreter.assertExpectedNonEagerOutput( + "handles-continue-in-deferred-for-loop/test.expected" + ); } @Test diff --git a/src/test/resources/eager/handles-break-in-deferred-for-loop/test.expected.expected.jinja b/src/test/resources/eager/handles-break-in-deferred-for-loop/test.expected.expected.jinja new file mode 100644 index 000000000..4df3001da --- /dev/null +++ b/src/test/resources/eager/handles-break-in-deferred-for-loop/test.expected.expected.jinja @@ -0,0 +1,4 @@ +Start loop +i is: 0 +i is: 1 +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-break-in-deferred-for-loop/test.expected.jinja b/src/test/resources/eager/handles-break-in-deferred-for-loop/test.expected.jinja new file mode 100644 index 000000000..c7677739d --- /dev/null +++ b/src/test/resources/eager/handles-break-in-deferred-for-loop/test.expected.jinja @@ -0,0 +1,8 @@ +Start loop +{% for i in deferred %}\ + {% if i > 1 %}\ + {% break '' %}\ + {% endif %}\ + i is: {{ i }} +{% endfor %}\ +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-break-in-deferred-for-loop/test.jinja b/src/test/resources/eager/handles-break-in-deferred-for-loop/test.jinja index 7351688c7..8cd047c06 100644 --- a/src/test/resources/eager/handles-break-in-deferred-for-loop/test.jinja +++ b/src/test/resources/eager/handles-break-in-deferred-for-loop/test.jinja @@ -1,8 +1,8 @@ Start loop -{% for i in deferred %} - {% if i %} - {% break %} - {% endif %} +{% for i in deferred -%} + {% if i > 1 -%} + {% break -%} + {% endif -%} i is: {{ i }} -{% endfor %} +{% endfor -%} End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.expected.expected.jinja b/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.expected.expected.jinja new file mode 100644 index 000000000..a134c702a --- /dev/null +++ b/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.expected.expected.jinja @@ -0,0 +1,5 @@ +Start loop +i is: 1 +i is: 3 +i is: 5 +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.expected.jinja b/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.expected.jinja new file mode 100644 index 000000000..c2ba0a12c --- /dev/null +++ b/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.expected.jinja @@ -0,0 +1,8 @@ +Start loop +{% for i in deferred %}\ + {% if i % 2 == 0 %}\ + {% continue '' %}\ + {% endif %}\ + i is: {{ i }} +{% endfor %}\ +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.jinja b/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.jinja index 28624a299..710517afa 100644 --- a/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.jinja +++ b/src/test/resources/eager/handles-continue-in-deferred-for-loop/test.jinja @@ -1,8 +1,8 @@ Start loop -{% for i in deferred %} - {% if i %} - {% continue %} - {% endif %} +{% for i in deferred -%} + {% if i % 2 == 0 -%} + {% continue -%} + {% endif -%} i is: {{ i }} -{% endfor %} +{% endfor -%} End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-break-in-for-loop/test.expected.expected.jinja b/src/test/resources/eager/handles-deferred-break-in-for-loop/test.expected.expected.jinja new file mode 100644 index 000000000..4df3001da --- /dev/null +++ b/src/test/resources/eager/handles-deferred-break-in-for-loop/test.expected.expected.jinja @@ -0,0 +1,4 @@ +Start loop +i is: 0 +i is: 1 +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-break-in-for-loop/test.expected.jinja b/src/test/resources/eager/handles-deferred-break-in-for-loop/test.expected.jinja new file mode 100644 index 000000000..b37cf88b3 --- /dev/null +++ b/src/test/resources/eager/handles-deferred-break-in-for-loop/test.expected.jinja @@ -0,0 +1,24 @@ +Start loop +{% for __ignored__ in [0] %}\ +{% if 0 > deferred %}\ + {% break '' %}\ + {% endif %}\ +i is: 0 +{% if 1 > deferred %}\ + {% break '' %}\ + {% endif %}\ +i is: 1 +{% if 2 > deferred %}\ + {% break '' %}\ + {% endif %}\ +i is: 2 +{% if 3 > deferred %}\ + {% break '' %}\ + {% endif %}\ +i is: 3 +{% if 4 > deferred %}\ + {% break '' %}\ + {% endif %}\ + i is: 4 +{% endfor %}\ +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-break-in-for-loop/test.jinja b/src/test/resources/eager/handles-deferred-break-in-for-loop/test.jinja index 6cdda8f80..65718591e 100644 --- a/src/test/resources/eager/handles-deferred-break-in-for-loop/test.jinja +++ b/src/test/resources/eager/handles-deferred-break-in-for-loop/test.jinja @@ -1,8 +1,8 @@ Start loop -{% for i in range(5) %} - {% if deferred %} - {% break %} - {% endif %} +{% for i in range(5) -%} + {% if i > deferred -%} + {% break -%} + {% endif -%} i is: {{ i }} -{% endfor %} +{% endfor -%} End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.expected.expected.jinja b/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.expected.expected.jinja new file mode 100644 index 000000000..d1616c7c9 --- /dev/null +++ b/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.expected.expected.jinja @@ -0,0 +1,4 @@ +Start loop +i is: 1 +i is: 3 +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.expected.jinja b/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.expected.jinja new file mode 100644 index 000000000..52f85138e --- /dev/null +++ b/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.expected.jinja @@ -0,0 +1,8 @@ +Start loop +{% for i in [0, 1, 2, 3, 4] %}\ + {% if i % deferred == 0 %}\ + {% continue '' %}\ + {% endif %}\ + i is: {{ i }} +{% endfor %}\ +End loop \ No newline at end of file diff --git a/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.jinja b/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.jinja index 3025b150e..07173400f 100644 --- a/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.jinja +++ b/src/test/resources/eager/handles-deferred-continue-in-for-loop/test.jinja @@ -1,8 +1,8 @@ Start loop -{% for i in range(5) %} - {% if deferred %} - {% continue %} - {% endif %} +{% for i in range(5) -%} + {% if i % deferred == 0 -%} + {% continue -%} + {% endif -%} i is: {{ i }} -{% endfor %} +{% endfor -%} End loop \ No newline at end of file