diff --git a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java index b7ed9baca..654960ade 100644 --- a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java +++ b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java @@ -108,7 +108,7 @@ default boolean isFailOnUnknownTokens() { @Value.Default default boolean isNestedInterpretationEnabled() { - return true; + return false; // Default changed to false in 3.0 } @Value.Default diff --git a/src/test/java/com/hubspot/jinjava/lib/expression/EagerExpressionStrategyTest.java b/src/test/java/com/hubspot/jinjava/lib/expression/EagerExpressionStrategyTest.java index 9eae2ff80..ca40c32ab 100644 --- a/src/test/java/com/hubspot/jinjava/lib/expression/EagerExpressionStrategyTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/expression/EagerExpressionStrategyTest.java @@ -3,8 +3,11 @@ import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.hubspot.jinjava.Jinjava; import com.hubspot.jinjava.JinjavaConfig; import com.hubspot.jinjava.LegacyOverrides; +import com.hubspot.jinjava.interpret.Context; import com.hubspot.jinjava.interpret.Context.Library; import com.hubspot.jinjava.interpret.DeferredValue; import com.hubspot.jinjava.interpret.JinjavaInterpreter; @@ -14,15 +17,24 @@ import com.hubspot.jinjava.objects.collections.PyList; import com.hubspot.jinjava.tree.ExpressionNodeTest; import java.util.ArrayList; -import java.util.Collections; -import org.junit.After; import org.junit.Before; import org.junit.Test; public class EagerExpressionStrategyTest extends ExpressionNodeTest { + private Jinjava jinjava; + + class EagerExecutionModeNoRaw extends EagerExecutionMode { + + @Override + public boolean isPreserveRawTags() { + return false; // So that we can run all the ExpressionNodeTest tests without having the extra `{% raw %}` tags inserted + } + } + @Before public void eagerSetup() throws Exception { + jinjava = new Jinjava(JinjavaConfig.newBuilder().build()); jinjava .getGlobalContext() .registerFunction( @@ -35,19 +47,27 @@ public void eagerSetup() throws Exception { interpreter = new JinjavaInterpreter( jinjava, - context, + new Context(), JinjavaConfig .newBuilder() + .withExecutionMode(new EagerExecutionModeNoRaw()) + .build() + ); + nestedInterpreter = + new JinjavaInterpreter( + jinjava, + interpreter.getContext(), + JinjavaConfig + .newBuilder() + .withNestedInterpretationEnabled(true) + .withLegacyOverrides( + LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build() + ) .withExecutionMode(EagerExecutionMode.instance()) .build() ); - JinjavaInterpreter.pushCurrent(interpreter); - context.put("deferred", DeferredValue.instance()); - } - - @After - public void teardown() { - JinjavaInterpreter.popCurrent(); + interpreter.getContext().put("deferred", DeferredValue.instance()); + nestedInterpreter.getContext().put("deferred", DeferredValue.instance()); } @Test @@ -55,31 +75,24 @@ public void itPreservesRawTags() { interpreter = new JinjavaInterpreter( jinjava, - context, + new Context(), JinjavaConfig .newBuilder() - .withNestedInterpretationEnabled(false) - .withLegacyOverrides( - LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build() - ) .withExecutionMode(EagerExecutionMode.instance()) .build() ); - JinjavaInterpreter.pushCurrent(interpreter); - try { - assertExpectedOutput( - "{{ '{{ foo }}' }} {{ '{% something %}' }} {{ 'not needed' }}", - "{% raw %}{{ foo }}{% endraw %} {% raw %}{% something %}{% endraw %} not needed" - ); - } finally { - JinjavaInterpreter.popCurrent(); - } + assertExpectedOutput( + interpreter, + "{{ '{{ foo }}' }} {{ '{% something %}' }} {{ 'not needed' }}", + "{% raw %}{{ foo }}{% endraw %} {% raw %}{% something %}{% endraw %} not needed" + ); } @Test public void itPreservesRawTagsNestedInterpretation() { - context.put("bar", "bar"); + nestedInterpreter.getContext().put("bar", "bar"); assertExpectedOutput( + nestedInterpreter, "{{ '{{ 12345 }}' }} {{ '{% print bar %}' }} {{ 'not needed' }}", "12345 bar not needed" ); @@ -88,6 +101,7 @@ public void itPreservesRawTagsNestedInterpretation() { @Test public void itPrependsMacro() { assertExpectedOutput( + interpreter, "{% macro foo(bar) %} {{ bar }} {% endmacro %}{{ foo(deferred) }}", "{% macro foo(bar) %} {{ bar }} {% endmacro %}{{ foo(deferred) }}" ); @@ -95,8 +109,9 @@ public void itPrependsMacro() { @Test public void itPrependsSet() { - context.put("foo", new PyList(new ArrayList<>())); + interpreter.getContext().put("foo", new PyList(new ArrayList<>())); assertExpectedOutput( + interpreter, "{{ foo.append(deferred) }}", "{% set foo = [] %}{{ foo.append(deferred) }}" ); @@ -104,8 +119,9 @@ public void itPrependsSet() { @Test public void itDoesConcatenation() { - context.put("foo", "y'all"); + interpreter.getContext().put("foo", "y'all"); assertExpectedOutput( + interpreter, "{{ 'oh, ' ~ foo ~ foo ~ ' toaster' }}", "oh, y'ally'all toaster" ); @@ -116,6 +132,7 @@ public void itHandlesQuotesLikeJinja() { // {{ 'a|\'|\\\'|\\\\\'|"|\"|\\"|\\\\"|a ' ~ " b|\"|\\\"|\\\\\"|'|\'|\\'|\\\\'|b" }} // --> a|'|\'|\\'|"|"|\"|\\"|a b|"|\"|\\"|'|'|\'|\\'|b assertExpectedOutput( + interpreter, "{{ 'a|\\'|\\\\\\'|\\\\\\\\\\'|\"|\\\"|\\\\\"|\\\\\\\\\"|a ' " + "~ \" b|\\\"|\\\\\\\"|\\\\\\\\\\\"|'|\\'|\\\\'|\\\\\\\\'|b\" }}", "a|'|\\'|\\\\'|\"|\"|\\\"|\\\\\"|a b|\"|\\\"|\\\\\"|'|'|\\'|\\\\'|b" @@ -125,6 +142,7 @@ public void itHandlesQuotesLikeJinja() { @Test public void itGoesIntoDeferredExecutionMode() { assertExpectedOutput( + interpreter, "{{ is_deferred_execution_mode() }}" + "{% if deferred %}{{ is_deferred_execution_mode() }}{% endif %}" + "{{ is_deferred_execution_mode() }}", @@ -135,6 +153,7 @@ public void itGoesIntoDeferredExecutionMode() { @Test public void itGoesIntoDeferredExecutionModeWithMacro() { assertExpectedOutput( + interpreter, "{% macro def() %}{{ is_deferred_execution_mode() }}{% endmacro %}" + "{{ def() }}" + "{% if deferred %}{{ def() }}{% endif %}" + @@ -145,20 +164,28 @@ public void itGoesIntoDeferredExecutionModeWithMacro() { @Test public void itDoesNotGoIntoDeferredExecutionModeUnnecessarily() { - assertExpectedOutput("{{ is_deferred_execution_mode() }}", "false"); + assertExpectedOutput(interpreter, "{{ is_deferred_execution_mode() }}", "false"); interpreter.getContext().setDeferredExecutionMode(true); - assertExpectedOutput("{{ is_deferred_execution_mode() }}", "true"); + assertExpectedOutput(interpreter, "{{ is_deferred_execution_mode() }}", "true"); } @Test public void itDoesNotNestedInterpretIfThereAreFakeNotes() { - assertExpectedOutput("{{ '{#something_to_{{keep}}' }}", "{#something_to_{{keep}}"); + assertExpectedOutput( + nestedInterpreter, + "{{ '{#something_to_{{keep}}' }}", + "{#something_to_{{keep}}" + ); } @Test public void itDoesNotReconstructWithDoubleCurlyBraces() { interpreter.getContext().put("foo", ImmutableMap.of("foo", ImmutableMap.of())); - assertExpectedOutput("{{ deferred ~ foo }}", "{{ deferred ~ {'foo': {} } }}"); + assertExpectedOutput( + interpreter, + "{{ deferred ~ foo }}", + "{{ deferred ~ {'foo': {} } }}" + ); } @Test @@ -167,6 +194,7 @@ public void itDoesNotReconstructWithNestedDoubleCurlyBraces() { .getContext() .put("foo", ImmutableMap.of("foo", ImmutableMap.of("bar", ImmutableMap.of()))); assertExpectedOutput( + interpreter, "{{ deferred ~ foo }}", "{{ deferred ~ {'foo': {'bar': {} } } }}" ); @@ -175,6 +203,7 @@ public void itDoesNotReconstructWithNestedDoubleCurlyBraces() { @Test public void itDoesNotReconstructDirectlyWrittenWithDoubleCurlyBraces() { assertExpectedOutput( + interpreter, "{{ deferred ~ {\n'foo': {\n'bar': deferred\n}\n}\n }}", "{{ deferred ~ {'foo': {'bar': deferred} } }}" ); @@ -184,6 +213,7 @@ public void itDoesNotReconstructDirectlyWrittenWithDoubleCurlyBraces() { public void itReconstructsWithNestedInterpretation() { interpreter.getContext().put("foo", "{{ print 'bar' }}"); assertExpectedOutput( + interpreter, "{{ deferred ~ foo }}", "{{ deferred ~ '{{ print \\'bar\\' }}' }}" ); @@ -192,18 +222,24 @@ public void itReconstructsWithNestedInterpretation() { @Test public void itDoesNotDoNestedInterpretationWithSyntaxErrors() { try ( - InterpreterScopeClosable c = interpreter.enterScope( - ImmutableMap.of(Library.TAG, Collections.singleton("print")) + InterpreterScopeClosable c = nestedInterpreter.enterScope( + ImmutableMap.of(Library.TAG, ImmutableSet.of("print")) ) ) { - interpreter.getContext().put("foo", "{% print 'bar' %}"); + nestedInterpreter.getContext().put("foo", "{% print 'bar' %}"); // Rather than rendering this to an empty string - assertThat(interpreter.render("{{ foo }}")).isEqualTo("{% print 'bar' %}"); + assertExpectedOutput(nestedInterpreter, "{{ foo }}", "{% print 'bar' %}"); } } - private void assertExpectedOutput(String inputTemplate, String expectedOutput) { - assertThat(interpreter.render(inputTemplate)).isEqualTo(expectedOutput); + private void assertExpectedOutput( + JinjavaInterpreter interpreter, + String inputTemplate, + String expectedOutput + ) { + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + assertThat(a.value().render(inputTemplate)).isEqualTo(expectedOutput); + } } public static boolean isDeferredExecutionMode() { diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/MacroTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/MacroTagTest.java index 108204510..d351ac29c 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/MacroTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/MacroTagTest.java @@ -282,38 +282,41 @@ public void itEnforcesMacroRecursionWithMaxDepth() throws IOException { .build() ) .newInterpreter(); - JinjavaInterpreter.pushCurrent(interpreter); - - try { + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { String template = fixtureText("ending-recursion"); String out = interpreter.render(template); assertThat(interpreter.getErrorsCopy().get(0).getMessage()) .contains("Max recursion limit of 2 reached for macro 'hello'"); assertThat(out).contains("Hello Hello"); - } finally { - JinjavaInterpreter.popCurrent(); } } @Test public void itPreventsRecursionForMacroWithVar() { - String jinja = - "{%- macro func(var) %}" + - "{%- for f in var %}" + - "{{ f.val }}" + - "{%- endfor %}" + - "{%- endmacro %}" + - "{%- set var = {" + - " 'f' : {" + - " 'val': '{{ self }}'," + - " }" + - "} %}" + - "{% set self='{{var}}' %}" + - "{{ func(var) }}" + - ""; - Node node = new TreeParser(interpreter, jinja).buildTree(); - assertThat(interpreter.render(node)) - .isEqualTo("{'f': {'val': '{'f': {'val': '{{ self }}'} }'} }"); + interpreter = + new Jinjava( + JinjavaConfig.newBuilder().withNestedInterpretationEnabled(true).build() + ) + .newInterpreter(); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + String jinja = + "{%- macro func(var) %}" + + "{%- for k,f in var.items() %}" + + "{{ f.val }}" + + "{%- endfor %}" + + "{%- endmacro %}" + + "{%- set var = {" + + " 'f' : {" + + " 'val': '{{ self }}'," + + " }" + + "} %}" + + "{% set self='{{var}}' %}" + + "{{ func(var) }}" + + ""; + Node node = new TreeParser(interpreter, jinja).buildTree(); + assertThat(interpreter.render(node)) + .isEqualTo("{'f': {'val': '{'f': {'val': '{{ self }}'} }'} }"); + } } @Test diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerExtendsTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerExtendsTagTest.java index 539996803..b3b2857fc 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerExtendsTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerExtendsTagTest.java @@ -22,12 +22,18 @@ public class EagerExtendsTagTest extends ExtendsTagTest { @Before public void eagerSetup() { + eagerSetup(false); + } + + void eagerSetup(boolean nestedInterpretation) { + JinjavaInterpreter.popCurrent(); interpreter = new JinjavaInterpreter( jinjava, context, JinjavaConfig .newBuilder() + .withNestedInterpretationEnabled(nestedInterpretation) .withExecutionMode(EagerExecutionMode.instance()) .withLegacyOverrides( LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build() @@ -66,6 +72,7 @@ public void itDefersBlockInExtendsChildSecondPass() { @Test public void itDefersSuperBlockWithDeferred() { + eagerSetup(true); expectedTemplateInterpreter.assertExpectedOutputNonIdempotent( "defers-super-block-with-deferred" ); @@ -73,16 +80,30 @@ public void itDefersSuperBlockWithDeferred() { @Test public void itDefersSuperBlockWithDeferredSecondPass() { + eagerSetup(true); context.put("deferred", "Resolved now"); - expectedTemplateInterpreter.assertExpectedOutput( - "defers-super-block-with-deferred.expected" - ); - context.remove(RelativePathResolver.CURRENT_PATH_CONTEXT_KEY); expectedTemplateInterpreter.assertExpectedNonEagerOutput( "defers-super-block-with-deferred.expected" ); } + @Test + public void itDefersSuperBlockWithDeferredNestedInterp() { + eagerSetup(true); + expectedTemplateInterpreter.assertExpectedOutputNonIdempotent( + "defers-super-block-with-deferred-nested-interp" + ); + } + + @Test + public void itDefersSuperBlockWithDeferredNestedInterpSecondPass() { + eagerSetup(true); + context.put("deferred", "Resolved now"); + expectedTemplateInterpreter.assertExpectedOutput( + "defers-super-block-with-deferred-nested-interp.expected" + ); + } + @Test public void itReconstructsDeferredOutsideBlock() { expectedTemplateInterpreter.assertExpectedOutputNonIdempotent( diff --git a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java index 031f7183d..e1d578f95 100644 --- a/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerImportTagTest.java @@ -570,10 +570,11 @@ public void itHandlesVarFromImportedMacro() { "a" + "{% endset %}" + "{{ __macro_adjust_108896029_temp_variable_0__ }}\n" + - "{% for __ignored__ in [0] %}" + + "{% set __macro_adjust_108896029_temp_variable_1__ %}" + "{% do var.append('b' ~ deferred) %}" + "b" + - "{% endfor %}\n" + + "{% endset %}" + + "{{ __macro_adjust_108896029_temp_variable_1__ }}\n" + "c{{ var }}" ); context.put("deferred", "resolved"); diff --git a/src/test/java/com/hubspot/jinjava/tree/ExpressionNodeTest.java b/src/test/java/com/hubspot/jinjava/tree/ExpressionNodeTest.java index 23b482228..b53d0a7a9 100644 --- a/src/test/java/com/hubspot/jinjava/tree/ExpressionNodeTest.java +++ b/src/test/java/com/hubspot/jinjava/tree/ExpressionNodeTest.java @@ -5,131 +5,171 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.common.io.Resources; -import com.hubspot.jinjava.BaseInterpretingTest; import com.hubspot.jinjava.Jinjava; import com.hubspot.jinjava.JinjavaConfig; +import com.hubspot.jinjava.features.BuiltInFeatures; import com.hubspot.jinjava.features.FeatureConfig; import com.hubspot.jinjava.features.FeatureStrategies; -import com.hubspot.jinjava.interpret.Context; import com.hubspot.jinjava.interpret.JinjavaInterpreter; import com.hubspot.jinjava.interpret.UnknownTokenException; import java.nio.charset.StandardCharsets; +import org.junit.Before; import org.junit.Test; -public class ExpressionNodeTest extends BaseInterpretingTest { +public class ExpressionNodeTest { + + protected JinjavaInterpreter nestedInterpreter; + protected JinjavaInterpreter interpreter; + + @Before + public void setup() { + nestedInterpreter = + new Jinjava( + JinjavaConfig.newBuilder().withNestedInterpretationEnabled(true).build() + ) + .newInterpreter(); + interpreter = new Jinjava(JinjavaConfig.newBuilder().build()).newInterpreter(); + } @Test public void itRendersResultAsTemplateWhenContainingVarBlocks() throws Exception { - context.put("myvar", "hello {{ place }}"); - context.put("place", "world"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "hello {{ place }}"); + nestedInterpreter.getContext().put("place", "world"); - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()).isEqualTo("hello world"); + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()).isEqualTo("hello world"); + } } @Test - public void itRendersResultWithoutNestedExpressionInterpretation() throws Exception { - final JinjavaConfig config = JinjavaConfig - .newBuilder() - .withNestedInterpretationEnabled(false) - .build(); - JinjavaInterpreter noNestedInterpreter = new Jinjava(config).newInterpreter(); - Context contextNoNestedInterpretation = noNestedInterpreter.getContext(); - contextNoNestedInterpretation.put("myvar", "hello {{ place }}"); - contextNoNestedInterpretation.put("place", "world"); - - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(noNestedInterpreter).toString()) - .isEqualTo("hello {{ place }}"); + public void itRendersResultWithNestedExpressionInterpretation() throws Exception { + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "hello {{ place }}"); + nestedInterpreter.getContext().put("place", "world"); + + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()).isEqualTo("hello world"); + } } @Test - public void itRendersWithNestedExpressionInterpretationByDefault() throws Exception { - final JinjavaConfig config = JinjavaConfig.newBuilder().build(); - JinjavaInterpreter noNestedInterpreter = new Jinjava(config).newInterpreter(); - Context contextNoNestedInterpretation = noNestedInterpreter.getContext(); - contextNoNestedInterpretation.put("myvar", "hello {{ place }}"); - contextNoNestedInterpretation.put("place", "world"); - - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(noNestedInterpreter).toString()).isEqualTo("hello world"); + public void itRendersWithoutNestedExpressionInterpretationByDefault() throws Exception { + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + interpreter.getContext().put("myvar", "hello {{ place }}"); + interpreter.getContext().put("place", "world"); + + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(interpreter).toString()).isEqualTo("hello {{ place }}"); + } } @Test public void itRendersNestedTags() throws Exception { - final JinjavaConfig config = JinjavaConfig.newBuilder().build(); - JinjavaInterpreter jinjava = new Jinjava(config).newInterpreter(); - Context context = jinjava.getContext(); - context.put("myvar", "hello {% if (true) %}nasty{% endif %}"); + final JinjavaConfig config = JinjavaConfig + .newBuilder() + .withNestedInterpretationEnabled(true) + .build(); + try (var a = JinjavaInterpreter.closeablePushCurrent(nestedInterpreter).get()) { + nestedInterpreter + .getContext() + .put("myvar", "hello {% if (true) %}nasty{% endif %}"); - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(jinjava).toString()).isEqualTo("hello nasty"); + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()).isEqualTo("hello nasty"); + } } @Test public void itAvoidsInfiniteRecursionWhenVarsContainBraceBlocks() throws Exception { - context.put("myvar", "hello {{ place }}"); - context.put("place", "{{ place }}"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + interpreter.getContext().put("myvar", "hello {{ place }}"); + interpreter.getContext().put("place", "{{ place }}"); - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()).isEqualTo("hello {{ place }}"); + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(interpreter).toString()).isEqualTo("hello {{ place }}"); + } } @Test public void itAllowsNestedTagExpressions() throws Exception { - context.put("myvar", "{% if true %}{{ place }}{% endif %}"); - context.put("place", "{% if true %}Hello{% endif %}"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "{% if true %}{{ place }}{% endif %}"); + nestedInterpreter.getContext().put("place", "{% if true %}Hello{% endif %}"); - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()).isEqualTo("Hello"); + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()).isEqualTo("Hello"); + } } @Test public void itAvoidsInfiniteRecursionWhenVarsAreInIfBlocks() throws Exception { - context.put("myvar", "{% if true %}{{ place }}{% endif %}"); - context.put("place", "{% if true %}{{ myvar }}{% endif %}"); - - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()) - .isEqualTo("{% if true %}{{ myvar }}{% endif %}"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "{% if true %}{{ place }}{% endif %}"); + nestedInterpreter.getContext().put("place", "{% if true %}{{ myvar }}{% endif %}"); + + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()) + .isEqualTo("{% if true %}{{ myvar }}{% endif %}"); + } } @Test public void itDoesNotRescursivelyEvaluateExpressionsOfSelf() throws Exception { - context.put("myvar", "hello {{myvar}}"); - - ExpressionNode node = fixture("simplevar"); - // It renders once, and then stop further rendering after detecting recursion. - assertThat(node.render(interpreter).toString()).isEqualTo("hello hello {{myvar}}"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "hello {{myvar}}"); + + ExpressionNode node = fixture("simplevar"); + // It renders once, and then stop further rendering after detecting recursion. + assertThat(node.render(nestedInterpreter).toString()) + .isEqualTo("hello hello {{myvar}}"); + } } @Test public void itDoesNotRescursivelyEvaluateExpressions() throws Exception { - context.put("myvar", "hello {{ place }}"); - context.put("place", "{{location}}"); - context.put("location", "this is a place."); - - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()).isEqualTo("hello this is a place."); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "hello {{ place }}"); + nestedInterpreter.getContext().put("place", "{{location}}"); + nestedInterpreter.getContext().put("location", "this is a place."); + + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()) + .isEqualTo("hello this is a place."); + } } @Test public void itDoesNotRescursivelyEvaluateMoreExpressions() throws Exception { - context.put("myvar", "hello {{ place }}"); - context.put("place", "there, {{ location }}"); - context.put("location", "this is {{ place }}"); - - ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()) - .isEqualTo("hello there, this is {{ place }}"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "hello {{ place }}"); + nestedInterpreter.getContext().put("place", "there, {{ location }}"); + nestedInterpreter.getContext().put("location", "this is {{ place }}"); + + ExpressionNode node = fixture("simplevar"); + assertThat(node.render(nestedInterpreter).toString()) + .isEqualTo("hello there, this is {{ place }}"); + } } @Test public void itRendersStringRange() throws Exception { - context.put("theString", "1234567890"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + interpreter.getContext().put("theString", "1234567890"); - ExpressionNode node = fixture("string-range"); - assertThat(node.render(interpreter).toString()).isEqualTo("345"); + ExpressionNode node = fixture("string-range"); + assertThat(node.render(interpreter).toString()).isEqualTo("345"); + } } @Test @@ -140,19 +180,25 @@ public void itRenderEchoUndefined() { FeatureConfig.newBuilder().add(ECHO_UNDEFINED, FeatureStrategies.ACTIVE).build() ) .build(); - final JinjavaInterpreter jinjavaInterpreter = new Jinjava(config).newInterpreter(); - jinjavaInterpreter.getContext().put("subject", "this"); - - String template = - "{{ subject | capitalize() }} expression {{ testing.template('hello_world') }} " + - "has a {{ unknown | lower() }} " + - "token but {{ unknown | default(\"replaced\") }} and empty {{ '' }}"; - Node node = new TreeParser(jinjavaInterpreter, template).buildTree(); - assertThat(jinjavaInterpreter.render(node)) - .isEqualTo( - "This expression {{ testing.template('hello_world') }} " + - "has a {{ unknown | lower() }} token but replaced and empty " - ); + try ( + var a = JinjavaInterpreter + .closeablePushCurrent(new Jinjava(config).newInterpreter()) + .get() + ) { + JinjavaInterpreter jinjavaInterpreter = a.value(); + jinjavaInterpreter.getContext().put("subject", "this"); + + String template = + "{{ subject | capitalize() }} expression {{ testing.template('hello_world') }} " + + "has a {{ unknown | lower() }} " + + "token but {{ unknown | default(\"replaced\") }} and empty {{ '' }}"; + Node node = new TreeParser(jinjavaInterpreter, template).buildTree(); + assertThat(jinjavaInterpreter.render(node)) + .isEqualTo( + "This expression {{ testing.template('hello_world') }} " + + "has a {{ unknown | lower() }} token but replaced and empty " + ); + } } @Test @@ -161,13 +207,18 @@ public void itFailsOnUnknownTokensVariables() throws Exception { .newBuilder() .withFailOnUnknownTokens(true) .build(); - JinjavaInterpreter jinjavaInterpreter = new Jinjava(config).newInterpreter(); - - String jinja = "{{ UnknownToken }}"; - Node node = new TreeParser(jinjavaInterpreter, jinja).buildTree(); - assertThatThrownBy(() -> jinjavaInterpreter.render(node)) - .isInstanceOf(UnknownTokenException.class) - .hasMessage("Unknown token found: UnknownToken"); + try ( + var a = JinjavaInterpreter + .closeablePushCurrent(new Jinjava(config).newInterpreter()) + .get() + ) { + JinjavaInterpreter jinjavaInterpreter = a.value(); + String jinja = "{{ UnknownToken }}"; + Node node = new TreeParser(jinjavaInterpreter, jinja).buildTree(); + assertThatThrownBy(() -> jinjavaInterpreter.render(node)) + .isInstanceOf(UnknownTokenException.class) + .hasMessage("Unknown token found: UnknownToken"); + } } @Test @@ -191,13 +242,18 @@ public void itFailsOnUnknownTokensOfIf() throws Exception { .newBuilder() .withFailOnUnknownTokens(true) .build(); - JinjavaInterpreter jinjavaInterpreter = new Jinjava(config).newInterpreter(); - - String jinja = "{% if bad %} BAD {% endif %}"; - Node node = new TreeParser(jinjavaInterpreter, jinja).buildTree(); - assertThatThrownBy(() -> jinjavaInterpreter.render(node)) - .isInstanceOf(UnknownTokenException.class) - .hasMessageContaining("Unknown token found: bad"); + try ( + var a = JinjavaInterpreter + .closeablePushCurrent(new Jinjava(config).newInterpreter()) + .get() + ) { + JinjavaInterpreter jinjavaInterpreter = a.value(); + String jinja = "{% if bad %} BAD {% endif %}"; + Node node = new TreeParser(jinjavaInterpreter, jinja).buildTree(); + assertThatThrownBy(() -> jinjavaInterpreter.render(node)) + .isInstanceOf(UnknownTokenException.class) + .hasMessageContaining("Unknown token found: bad"); + } } @Test @@ -206,34 +262,45 @@ public void itFailsOnUnknownTokensWithFilter() throws Exception { .newBuilder() .withFailOnUnknownTokens(true) .build(); - JinjavaInterpreter jinjavaInterpreter = new Jinjava(config).newInterpreter(); - - String jinja = "{{ UnknownToken }}"; - Node node = new TreeParser(jinjavaInterpreter, jinja).buildTree(); - assertThatThrownBy(() -> jinjavaInterpreter.render(node)) - .isInstanceOf(UnknownTokenException.class) - .hasMessage("Unknown token found: UnknownToken"); + try ( + var a = JinjavaInterpreter + .closeablePushCurrent(new Jinjava(config).newInterpreter()) + .get() + ) { + JinjavaInterpreter jinjavaInterpreter = a.value(); + String jinja = "{{ UnknownToken }}"; + Node node = new TreeParser(jinjavaInterpreter, jinja).buildTree(); + assertThatThrownBy(() -> jinjavaInterpreter.render(node)) + .isInstanceOf(UnknownTokenException.class) + .hasMessage("Unknown token found: UnknownToken"); + } } @Test public void valueExprWithOr() throws Exception { - context.put("a", "foo"); - context.put("b", "bar"); - context.put("c", ""); - context.put("d", 0); - - assertThat(val("{{ a or b }}")).isEqualTo("foo"); - assertThat(val("{{ c or a }}")).isEqualTo("foo"); - assertThat(val("{{ d or b }}")).isEqualTo("bar"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + interpreter.getContext().put("a", "foo"); + interpreter.getContext().put("b", "bar"); + interpreter.getContext().put("c", ""); + interpreter.getContext().put("d", 0); + + assertThat(val("{{ a or b }}")).isEqualTo("foo"); + assertThat(val("{{ c or a }}")).isEqualTo("foo"); + assertThat(val("{{ d or b }}")).isEqualTo("bar"); + } } @Test public void itEscapesValueWhenContextSet() throws Exception { - context.put("a", "foo < bar"); - assertThat(val("{{ a }}")).isEqualTo("foo < bar"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + interpreter = a.value(); + interpreter.getContext().put("a", "foo < bar"); + assertThat(val("{{ a }}")).isEqualTo("foo < bar"); - context.setAutoEscape(true); - assertThat(val("{{ a }}")).isEqualTo("foo < bar"); + interpreter.getContext().setAutoEscape(true); + assertThat(val("{{ a }}")).isEqualTo("foo < bar"); + } } @Test @@ -243,19 +310,23 @@ public void itIgnoresParseErrorsWhenFeatureIsEnabled() { .withFeatureConfig( FeatureConfig .newBuilder() - .add(JinjavaInterpreter.IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS, c -> true) + .add( + BuiltInFeatures.IGNORE_NESTED_INTERPRETATION_PARSE_ERRORS, + FeatureStrategies.ACTIVE + ) .build() ) .build(); - JinjavaInterpreter interpreter = new Jinjava(config).newInterpreter(); - Context context = interpreter.getContext(); - context.put("myvar", "hello {% if"); - context.put("place", "world"); + try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) { + nestedInterpreter = a.value(); + nestedInterpreter.getContext().put("myvar", "hello {% if"); + nestedInterpreter.getContext().put("place", "world"); - ExpressionNode node = fixture("simplevar"); + ExpressionNode node = fixture("simplevar"); - assertThat(node.render(interpreter).toString()).isEqualTo("hello {% if"); - assertThat(interpreter.getErrors()).isEmpty(); + assertThat(node.render(nestedInterpreter).toString()).isEqualTo("hello {% if"); + assertThat(nestedInterpreter.getErrors()).isEmpty(); + } } private String val(String jinja) { diff --git a/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.expected.expected.jinja b/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.expected.expected.jinja new file mode 100644 index 000000000..a4e73e2b1 --- /dev/null +++ b/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.expected.expected.jinja @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.expected.jinja b/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.expected.jinja new file mode 100644 index 000000000..41b046b1c --- /dev/null +++ b/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.expected.jinja @@ -0,0 +1,13 @@ +{% set current_path = '../eager/extendstag/base-deferred.html' %}\ + + + + + diff --git a/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.jinja b/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.jinja new file mode 100644 index 000000000..cc301ad57 --- /dev/null +++ b/src/test/resources/tags/eager/extendstag/defers-super-block-with-deferred-nested-interp.jinja @@ -0,0 +1,6 @@ +{% extends "../eager/extendstag/base-deferred.html" %} + +{%- block sidebar -%} +

Table Of Contents

+{{ super() }} +{%- endblock -%}