You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/08/07 22:32:17 UTC

[15/21] incubator-freemarker git commit: FREEMARKER-63: Added isNestedContentSupported() to templateDirectiveModel

FREEMARKER-63: Added isNestedContentSupported() to templateDirectiveModel


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/5bd19ade
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/5bd19ade
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/5bd19ade

Branch: refs/heads/3
Commit: 5bd19adeb8dd24cde34987c4724c7a5d5d227f52
Parents: 589d9b8
Author: ddekany <dd...@apache.org>
Authored: Sun Jul 30 23:07:29 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Jul 30 23:07:29 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                                |  2 ++
 .../freemarker/core/DirectiveCallPlaceTest.java   | 16 ++++++++++++++++
 .../core/EnvironmentGetTemplateVariantsTest.java  |  6 ++++++
 .../core/TemplateCallableModelTest.java           |  4 ++++
 .../core/TheadInterruptingSupportTest.java        | 16 +++++++++++++++-
 .../core/userpkg/AllFeaturesDirective.java        |  6 ++++++
 .../core/userpkg/NamedVarargsOnlyDirective.java   |  7 +++++++
 .../userpkg/PositionalVarargsOnlyDirective.java   |  7 +++++++
 .../core/userpkg/TwoNamedParamsDirective.java     |  8 ++++++--
 .../userpkg/TwoNestedContentParamsDirective.java  |  6 ++++++
 .../userpkg/TwoPositionalParamsDirective.java     |  8 ++++++--
 .../core/userpkg/UpperCaseDirective.java          |  6 ++++++
 .../core/valueformat/NumberFormatTest.java        |  5 +++++
 .../core/templatesuite/expected/interpret.txt     |  4 +---
 .../core/templatesuite/templates/interpret.ftl    |  7 ++++---
 .../freemarker/core/ASTDynamicTopLevelCall.java   | 18 +++++++++++++-----
 .../freemarker/core/BuiltInsForStringsMisc.java   |  6 +++++-
 .../core/model/TemplateDirectiveModel.java        | 13 +++++++++++++
 .../apache/freemarker/servlet/IncludePage.java    |  5 +++++
 .../jsp/CustomTagAndELFunctionCombiner.java       | 10 ++++++++++
 .../servlet/jsp/SimpleTagDirectiveModel.java      |  5 +++++
 .../freemarker/servlet/jsp/TagDirectiveModel.java |  5 +++++
 .../test/templateutil/AssertDirective.java        |  5 +++++
 .../test/templateutil/AssertEqualsDirective.java  | 14 ++++++++++----
 .../test/templateutil/AssertFailsDirective.java   | 14 ++++++++++----
 .../test/templateutil/NoOutputDirective.java      |  6 ++++++
 26 files changed, 184 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index 6b347e5..938f9bf 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -108,6 +108,8 @@ Node: Changes already mentioned above aren't repeated here!
     of the `execute` method.
   - ?isTransform was removed (as there are no transforms anymore).
     Converter note: The template converter tool replaces it with ?isDirective
+- The directive returned by `?interpret` doesn't allow nested content anymore. (It wasn't useful earlier either;
+  the nested content was simply executed after the interpreted string.)
 
 Java API changes
 ================

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
index 94cd199..f959b1d 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DirectiveCallPlaceTest.java
@@ -142,6 +142,11 @@ public class DirectiveCallPlaceTest extends TemplateTest {
             return ArgumentArrayLayout.PARAMETERLESS;
         }
 
+        @Override
+        public boolean isNestedContentSupported() {
+            return true;
+        }
+
         protected abstract Class getTextConversionIdentity();
 
         private String convertBodyText(CallPlace callPlace, Environment env) throws TemplateException,
@@ -207,6 +212,11 @@ public class DirectiveCallPlaceTest extends TemplateTest {
             return ArgumentArrayLayout.PARAMETERLESS;
         }
 
+        @Override
+        public boolean isNestedContentSupported() {
+            return true;
+        }
+
         private String getTemplateSourceName(CallPlace callPlace) {
             return callPlace.getTemplate().getSourceName();
         }
@@ -239,6 +249,12 @@ public class DirectiveCallPlaceTest extends TemplateTest {
         public ArgumentArrayLayout getArgumentArrayLayout() {
             return ARGS_LAYOUT;
         }
+
+        @Override
+        public boolean isNestedContentSupported() {
+            return true;
+        }
+
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
index 40f7c63..10205e9 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/EnvironmentGetTemplateVariantsTest.java
@@ -211,6 +211,12 @@ public class EnvironmentGetTemplateVariantsTest extends TemplateTest {
             public ArgumentArrayLayout getArgumentArrayLayout() {
                 return ArgumentArrayLayout.PARAMETERLESS;
             }
+
+
+            @Override
+            public boolean isNestedContentSupported() {
+                return false;
+            }
         });
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
index a51c657..b7d55f5 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java
@@ -167,6 +167,10 @@ public class TemplateCallableModelTest extends TemplateTest {
         assertErrorContains("<@tncp ; i>${i}</@>", " 1 ", "\"i\"", " 2 ");
         assertOutput("<@tncp ; i, j>${i} ${j}</@>", "1 2");
         assertErrorContains("<@tncp ; i, j, k>${i}</@>", " 3 ", "\"i\", \"j\", \"k\"", " 2 ");
+
+        assertOutput("<@p...@p>",
+                "#p(p1=null, p2=null)");
+        assertErrorContains("<@p> </...@p>", "Nested content", "not supported");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
index 2ab270a..a6b01ac 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TheadInterruptingSupportTest.java
@@ -133,10 +133,14 @@ public class TheadInterruptingSupportTest {
             public ArgumentArrayLayout getArgumentArrayLayout() {
                 return ArgumentArrayLayout.PARAMETERLESS;
             }
+
+            @Override
+            public boolean isNestedContentSupported() {
+                return false;
+            }
         }
 
         public class CustomLoopDirective implements TemplateDirectiveModel {
-
             @Override
             public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                     throws TemplateException, IOException {
@@ -150,6 +154,11 @@ public class TheadInterruptingSupportTest {
             public ArgumentArrayLayout getArgumentArrayLayout() {
                 return ArgumentArrayLayout.PARAMETERLESS;
             }
+
+            @Override
+            public boolean isNestedContentSupported() {
+                return true;
+            }
         }
         
         public class SleepDirective implements TemplateDirectiveModel {
@@ -168,6 +177,11 @@ public class TheadInterruptingSupportTest {
             public ArgumentArrayLayout getArgumentArrayLayout() {
                 return ArgumentArrayLayout.PARAMETERLESS;
             }
+
+            @Override
+            public boolean isNestedContentSupported() {
+                return false;
+            }
         }
         
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
index e3be217..24a3a4e 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java
@@ -123,4 +123,10 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ARGS_LAYOUT;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
index aa69e8f..f978dbe 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/NamedVarargsOnlyDirective.java
@@ -56,4 +56,11 @@ public class NamedVarargsOnlyDirective extends TestTemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ARGS_LAYOUT;
     }
+
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
index cc3f9d8..b9d2b92 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/PositionalVarargsOnlyDirective.java
@@ -54,4 +54,11 @@ public class PositionalVarargsOnlyDirective extends TestTemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ARGS_LAYOUT;
     }
+
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
index dbff203..b4b85d3 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java
@@ -23,7 +23,6 @@ import java.io.IOException;
 import java.io.Writer;
 
 import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.NestedContentNotSupportedException;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.CallPlace;
@@ -55,7 +54,6 @@ public class TwoNamedParamsDirective extends TestTemplateDirectiveModel {
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
-        NestedContentNotSupportedException.check(callPlace);
         out.write("#n(");
         printParam(N1_ARG_NAME, args[N1_ARG_IDX], out, true);
         printParam(N2_ARG_NAME, args[N2_ARG_IDX], out);
@@ -66,4 +64,10 @@ public class TwoNamedParamsDirective extends TestTemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ARGS_LAYOUT;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
index eee1978..ff47315 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNestedContentParamsDirective.java
@@ -50,4 +50,10 @@ public class TwoNestedContentParamsDirective extends TestTemplateDirectiveModel
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ArgumentArrayLayout.PARAMETERLESS;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
index 52fe77e..4bd671e 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java
@@ -23,7 +23,6 @@ import java.io.IOException;
 import java.io.Writer;
 
 import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.NestedContentNotSupportedException;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.CallPlace;
@@ -44,7 +43,6 @@ public class TwoPositionalParamsDirective extends TestTemplateDirectiveModel {
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
-        NestedContentNotSupportedException.check(callPlace);
         out.write("#p(");
         printParam("p1", args[0], out, true);
         printParam("p2", args[1], out);
@@ -55,4 +53,10 @@ public class TwoPositionalParamsDirective extends TestTemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ARGS_LAYOUT;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
index 507b820..220aeef 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java
@@ -50,4 +50,10 @@ public class UpperCaseDirective implements TemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ArgumentArrayLayout.PARAMETERLESS;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
index 830a16c..318e7ae 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
@@ -189,6 +189,11 @@ public class NumberFormatTest extends TemplateTest {
             public ArgumentArrayLayout getArgumentArrayLayout() {
                 return ArgumentArrayLayout.PARAMETERLESS;
             }
+
+            @Override
+            public boolean isNestedContentSupported() {
+                return false;
+            }
         });
         assertOutput(
                 "<#assign s1 = n?string>"

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/interpret.txt
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/interpret.txt b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/interpret.txt
index fe862e6..0ecb929 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/interpret.txt
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/interpret.txt
@@ -16,8 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-abcdef
-abcdef
-abcdef
+abcabcabc
 
 M
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/interpret.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/interpret.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/interpret.ftl
index 41f8425..f0240e4 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/interpret.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/interpret.ftl
@@ -18,8 +18,9 @@
 -->
 <#global x=["a", "b", "c"]>
 <#global templateSource = r"<#list x as y>${y}</#list>">
-<@templateSource?interpret>def</@>
-<@[templateSource]?interpret>def</@>
-<@[templateSource,"id"]?interpret>def</@>
+<@templateSource?interpret />
+<@[templateSource]?interpret />
+<@[templateSource,"id"]?interpret />
+<@assertFails message="nested content"><@templateSource?interpret>x</@></@>
 
 <#assign t = '<#macro m>M</#macro>'?interpret><@t /><@m/>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
index eb06e23..32f14ca 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
@@ -45,7 +45,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  * AST node: {@code <@exp ...>}.
  * Executes a {@link TemplateCallableModel} that's embeddable directly into the static text (hence "top level"). At
  * least in the default template language the value must be a {@link TemplateDirectiveModel}, though technically
- * calling a {@link TemplateFunctionModel} is possible as well (hence it's not called "dynamic directive call").
+ * calling a {@link TemplateFunctionModel} is possible as well (hence this class is not called "dynamic directive
+ * call").
  * <p>
  * The {@link TemplateCallableModel} object is obtained on runtime by evaluating an expression, and the parameter list
  * is also validated (how many positional parameters are allowed, what named parameters are supported) then. Hence, the
@@ -99,12 +100,14 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
         TemplateCallableModel callableValue;
         TemplateDirectiveModel directive;
         TemplateFunctionModel function;
+        boolean nestedContentSupported;
         {
             TemplateModel callableValueTM = callableValueExp._eval(env);
             if (callableValueTM instanceof TemplateDirectiveModel) {
                 callableValue = (TemplateCallableModel) callableValueTM;
                 directive = (TemplateDirectiveModel) callableValueTM;
                 function = null;
+                nestedContentSupported = directive.isNestedContentSupported();
             } else if (callableValueTM instanceof TemplateFunctionModel) {
                 if (!allowCallingFunctions) {
                     // TODO [FM3][CF] Better exception
@@ -114,6 +117,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
                 callableValue = (TemplateCallableModel) callableValueTM;
                 directive = null;
                 function = (TemplateFunctionModel) callableValue;
+                nestedContentSupported = false;
             } else if (callableValueTM instanceof ASTDirMacro) {
                 // TODO [FM3][CF] Until macros were refactored to be TemplateDirectiveModel-s, we have this hack here.
                 ASTDirMacro macro = (ASTDirMacro) callableValueTM;
@@ -147,6 +151,10 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
             }
         }
 
+        if (!nestedContentSupported && hasNestedContent()) {
+            throw new _MiscTemplateException(env, "Nested content is not supported by this directive.");
+        }
+
         ArgumentArrayLayout argsLayout = callableValue.getArgumentArrayLayout();
         int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
         int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
@@ -175,7 +183,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
             }
             execArgs[posVarargsArgIdx] = varargsSeq;
         } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) {
-            throw new _MiscTemplateException(this,
+            throw new _MiscTemplateException(env,
                     "The target callable ",
                     (predefPosArgCnt != 0
                             ? new Object[] { "can only have ", predefPosArgCnt }
@@ -197,7 +205,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
                     if (namedVarargsHash == null) {
                         if (namedVarargsArgumentIndex == -1) {
                             Collection<String> validNames = predefNamedArgsMap.getKeys();
-                            throw new _MiscTemplateException(this,
+                            throw new _MiscTemplateException(env,
                                     validNames == null || validNames.isEmpty()
                                     ? new Object[] {
                                             "The target callable doesn't have any by-name-passed parameters (like ",
@@ -225,7 +233,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
         } else {
             TemplateModel result = function.execute(execArgs, env, this);
             if (result == null) {
-                throw new _MiscTemplateException(this, "Function has returned no value (or null)");
+                throw new _MiscTemplateException(env, "Function has returned no value (or null)");
             }
             // TODO [FM3][CF]
             throw new BugException("Top-level function call not yet implemented");
@@ -389,7 +397,7 @@ class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
         int nestedContentParamNamesSize = nestedContentParamNames != null ? nestedContentParamNames.size() : 0;
         int nestedContentParamValuesSize = nestedContentParamValues != null ? nestedContentParamValues.length : 0;
         if (nestedContentParamValuesSize != nestedContentParamNamesSize) {
-            throw new _MiscTemplateException(this,
+            throw new _MiscTemplateException(env,
                     "The invocation declares ", (nestedContentParamNamesSize != 0 ? nestedContentParamNamesSize : "no"),
                     " nested content parameter(s)",
                     (nestedContentParamNamesSize != 0

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index 4d15516..d9ad1bf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -200,7 +200,6 @@ class BuiltInsForStringsMisc {
             @Override
             public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
                     throws TemplateException, IOException {
-                // TODO [FM3] Disallow loop vars, and nested content
                 try {
                     boolean lastFIRE = env.setFastInvalidReferenceExceptions(false);
                     try {
@@ -222,6 +221,11 @@ class BuiltInsForStringsMisc {
             public ArgumentArrayLayout getArgumentArrayLayout() {
                 return ArgumentArrayLayout.PARAMETERLESS;
             }
+
+            @Override
+            public boolean isNestedContentSupported() {
+                return false;
+            }
         }
 
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
index 6995afe..3529c8c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
@@ -45,4 +45,17 @@ public interface TemplateDirectiveModel extends TemplateCallableModel {
     void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException;
 
+    /**
+     * Tells if this directive supports having nested content. If {@code false}, yet the caller specifies a non-empty
+     * (strictly 0-length, not even whitespace is allowed), FreeMarker will throw a {@link TemplateException} with
+     * descriptive error message, and {@link #execute(TemplateModel[], CallPlace, Writer, Environment)} won't be called.
+     * If {@code true}, the author of the directive shouldn't forget calling {@link
+     * CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)}, unless the intent was to skip the nested
+     * content. (This property was added to prevent the frequent oversight (in FreeMarker 2) where a directive that
+     * isn't supposed to have nested content doesn't examine if there's a nested content to throw an exception in that
+     * case. Then if there's nested content, it will be silently skipped during execution, as the directive never
+     * calls {@link CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)}.)
+     */
+    boolean isNestedContentSupported();
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
index c6adead..6199fcc 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
@@ -189,6 +189,11 @@ public class IncludePage implements TemplateDirectiveModel {
         return ARGS_LAYOUT;
     }
 
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
     private static final class CustomParamsRequest extends HttpServletRequestWrapper {
         private final HashMap paramsMap;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
index c51e611..2302f22 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
@@ -113,6 +113,11 @@ class CustomTagAndELFunctionCombiner {
         public ArgumentArrayLayout getArgumentArrayLayout() {
             return templateDirectiveModel.getArgumentArrayLayout();
         }
+
+        @Override
+        public boolean isNestedContentSupported() {
+            return templateDirectiveModel.isNestedContentSupported();
+        }
     }
 
     private static class TemplateDirectiveModelAndTemplateMethodModelEx extends CombinedTemplateModel
@@ -142,6 +147,11 @@ class CustomTagAndELFunctionCombiner {
         public ArgumentArrayLayout getArgumentArrayLayout() {
             return templateDirectiveModel.getArgumentArrayLayout();
         }
+
+        @Override
+        public boolean isNestedContentSupported() {
+            return templateDirectiveModel.isNestedContentSupported();
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
index 810a50c..4e7d6c7 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
@@ -111,6 +111,11 @@ class SimpleTagDirectiveModel extends JspTagModelBase implements TemplateDirecti
         return ARGS_LAYOUT;
     }
 
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
     static final class TemplateExceptionWrapperJspException extends JspException {
 
         public TemplateExceptionWrapperJspException(Throwable cause) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
index 661046f..86febfa 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
@@ -121,6 +121,11 @@ class TagDirectiveModel extends JspTagModelBase implements TemplateDirectiveMode
         return ARGS_LAYOUT;
     }
 
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
     /**
      * Implements extra methods to help mimicking JSP container behavior around the
      * {@link TemplateDirectiveModel#execute(TemplateModel[], CallPlace, Writer, Environment)} call.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
index 0a9e9cd..ffb2c40 100644
--- a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
+++ b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
@@ -70,4 +70,9 @@ public class AssertDirective implements TemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ARGS_LAYOUT;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
index a02308f..8334c20 100644
--- a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
+++ b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
@@ -80,6 +80,16 @@ public class AssertEqualsDirective implements TemplateDirectiveModel {
         }
     }
 
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return false;
+    }
+
     private String tryUnwrap(TemplateModel value) throws TemplateModelException {
         if (value == null) return "null";
             // This is the same order as comparison goes:
@@ -91,8 +101,4 @@ public class AssertEqualsDirective implements TemplateDirectiveModel {
         else return value.toString();
     }
 
-    @Override
-    public ArgumentArrayLayout getArgumentArrayLayout() {
-        return ARGS_LAYOUT;
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
index effc0e7..c089300 100644
--- a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
+++ b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
@@ -135,6 +135,16 @@ public class AssertFailsDirective implements TemplateDirectiveModel {
         }
     }
 
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
     private String getAsString(TemplateModel value, String paramName, Environment env)
             throws BadParameterTypeException, TemplateModelException {
         if (value == null) {
@@ -166,8 +176,4 @@ public class AssertFailsDirective implements TemplateDirectiveModel {
         }
     }
 
-    @Override
-    public ArgumentArrayLayout getArgumentArrayLayout() {
-        return ARGS_LAYOUT;
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5bd19ade/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
----------------------------------------------------------------------
diff --git a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
index b027974..7e53023 100644
--- a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
+++ b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
@@ -48,4 +48,10 @@ public class NoOutputDirective implements TemplateDirectiveModel {
     public ArgumentArrayLayout getArgumentArrayLayout() {
         return ArgumentArrayLayout.PARAMETERLESS;
     }
+
+    @Override
+    public boolean isNestedContentSupported() {
+        return true;
+    }
+
 }