You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/08/28 16:26:13 UTC

[groovy] 01/01: GROOVY-10731: run `MarkupTemplateEngine` type-checked not static compile

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit f743e3fd8c99f3c9948b91ea4a566ac2b49c4456
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Aug 28 09:41:57 2022 -0500

    GROOVY-10731: run `MarkupTemplateEngine` type-checked not static compile
---
 .../classgen/asm/indy/IndyCallSiteWriter.java      |  25 ++-
 .../asm/sc/StaticTypesWriterController.java        |  42 ++--
 .../groovy/text/markup/MarkupTemplateEngine.java   |  45 ++--
 .../groovy/text/MarkupTemplateEngineTest.groovy    | 248 ++++++++++-----------
 4 files changed, 173 insertions(+), 187 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java
index 4ab2ca6d6a..786f016dad 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/indy/IndyCallSiteWriter.java
@@ -18,6 +18,7 @@
  */
 package org.codehaus.groovy.classgen.asm.indy;
 
+import org.codehaus.groovy.GroovyBugError;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.classgen.asm.CallSiteWriter;
 import org.codehaus.groovy.classgen.asm.WriterController;
@@ -29,27 +30,28 @@ import org.codehaus.groovy.classgen.asm.WriterController;
  * call site caching is done by the jvm.
  */
 public class IndyCallSiteWriter extends CallSiteWriter {
-    private final WriterController controller;
-    public IndyCallSiteWriter(WriterController controller) {
-        super(controller);
-        this.controller = controller;
+
+    public IndyCallSiteWriter(final WriterController controller) {
+        super(controller); this.controller = controller;
     }
-    
+    private final WriterController controller;
+
     @Override
     public void generateCallSiteArray() {}
     @Override
-    public void makeCallSite(Expression receiver, String message,
-            Expression arguments, boolean safe, boolean implicitThis,
-            boolean callCurrent, boolean callStatic) {}
+    public void makeCallSite(Expression receiver, String message, Expression arguments, boolean safe, boolean implicitThis, boolean callCurrent, boolean callStatic) {}
     @Override
-    public void makeSingleArgumentCall(Expression receiver, String message, Expression arguments, boolean safe) {}
+    public void makeSingleArgumentCall(Expression receiver, String message, Expression arguments, boolean safe) {
+        throw new GroovyBugError("At line " + receiver.getLineNumber() + " column " + receiver.getColumnNumber() + "\n" +
+                "On receiver: " + receiver.getText() + " with message: " + message + " and arguments: " + arguments.getText() + "\n" +
+                "This method should not have been called. Please try to create a simple example reproducing this error and file a bug report at https://issues.apache.org/jira/browse/GROOVY");
+    }
     @Override
-    public void prepareCallSite(String message) {}    
+    public void prepareCallSite(String message) {}
     @Override
     public void makeSiteEntry() {}
     @Override
     public void makeCallSiteArrayInitializer() {}
-    
     @Override
     public void makeGetPropertySite(Expression receiver, String name, boolean safe, boolean implicitThis) {
         InvokeDynamicWriter idw = (InvokeDynamicWriter)controller.getInvocationWriter();
@@ -60,5 +62,4 @@ public class IndyCallSiteWriter extends CallSiteWriter {
         InvokeDynamicWriter idw = (InvokeDynamicWriter)controller.getInvocationWriter();
         idw.writeGetProperty(receiver, name, safe, implicitThis, true);
     }
-    
 }
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java
index 8aa352cef7..6cebe8970f 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesWriterController.java
@@ -44,7 +44,6 @@ import org.codehaus.groovy.transform.sc.StaticCompilationVisitor;
 import org.codehaus.groovy.transform.stc.StaticTypesMarker;
 import org.objectweb.asm.ClassVisitor;
 
-
 /**
  * An alternative {@link org.codehaus.groovy.classgen.asm.WriterController} which handles static types and method
  * dispatch. In case of a "mixed mode" where only some methods are annotated with {@link groovy.transform.TypeChecked}
@@ -53,19 +52,19 @@ import org.objectweb.asm.ClassVisitor;
 public class StaticTypesWriterController extends DelegatingController {
 
     protected boolean isInStaticallyCheckedMethod;
-    private StaticTypesCallSiteWriter callSiteWriter;
-    private StaticTypesStatementWriter statementWriter;
+
+    private LambdaWriter lambdaWriter;
+    private ClosureWriter closureWriter;
     private StaticTypesTypeChooser typeChooser;
     private StaticInvocationWriter invocationWriter;
-    private BinaryExpressionMultiTypeDispatcher binaryExprHelper;
+    private StaticTypesCallSiteWriter callSiteWriter;
+    private StaticTypesStatementWriter statementWriter;
     private UnaryExpressionHelper unaryExpressionHelper;
-    private ClosureWriter closureWriter;
-    private LambdaWriter lambdaWriter;
+    private BinaryExpressionMultiTypeDispatcher binaryExpressionHelper;
     private MethodReferenceExpressionWriter methodReferenceExpressionWriter;
 
-    public StaticTypesWriterController(WriterController normalController) {
-        super(normalController);
-        isInStaticallyCheckedMethod = false;
+    public StaticTypesWriterController(final WriterController controller) {
+        super(controller);
     }
 
     @Override
@@ -81,7 +80,7 @@ public class StaticTypesWriterController extends DelegatingController {
         this.unaryExpressionHelper = new StaticTypesUnaryExpressionHelper(this);
 
         CompilerConfiguration config = cn.getCompileUnit().getConfig();
-        this.binaryExprHelper = config.isIndyEnabled()
+        this.binaryExpressionHelper = config.isIndyEnabled()
                 ? new IndyStaticTypesMultiTypeDispatcher(this)
                 : new StaticTypesBinaryExpressionMultiTypeDispatcher(this);
     }
@@ -93,15 +92,14 @@ public class StaticTypesWriterController extends DelegatingController {
     }
 
     private void updateStaticCompileFlag(final MethodNode mn) {
+        AnnotatedNode outer = mn;
         ClassNode classNode = getClassNode();
-        AnnotatedNode node = mn;
-        boolean implementsGeneratedClosureOrGeneratedLambdaInterface = ClassHelper.isGeneratedFunction(classNode);
-        if (implementsGeneratedClosureOrGeneratedLambdaInterface) {
-            node = classNode.getOuterClass();
+        boolean inClosureOrLambda = ClassHelper.isGeneratedFunction(classNode);
+        if (inClosureOrLambda) {
+            outer = classNode.getOuterClass();
         }
-
-        isInStaticallyCheckedMethod = mn != null && (StaticCompilationVisitor.isStaticallyCompiled(node)
-                || implementsGeneratedClosureOrGeneratedLambdaInterface && Boolean.TRUE.equals(classNode.getNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE)));
+        boolean isStaticCompileNode = Boolean.TRUE.equals(classNode.getNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE));
+        isInStaticallyCheckedMethod = mn != null && (StaticCompilationVisitor.isStaticallyCompiled(outer) || inClosureOrLambda && isStaticCompileNode);
     }
 
     @Override
@@ -119,10 +117,8 @@ public class StaticTypesWriterController extends DelegatingController {
     @Override
     public CallSiteWriter getCallSiteWriter() {
         MethodNode methodNode = getMethodNode();
-        if (methodNode != null && Boolean.TRUE.equals(methodNode.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION))) {
-            return super.getCallSiteWriter();
-        }
-        if (isInStaticallyCheckedMethod) {
+        if (isInStaticallyCheckedMethod && (methodNode == null
+                || !Boolean.TRUE.equals(methodNode.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION)))) {
             return callSiteWriter;
         }
         return super.getCallSiteWriter();
@@ -137,7 +133,7 @@ public class StaticTypesWriterController extends DelegatingController {
         if (isInStaticallyCheckedMethod) {
             return statementWriter;
         } else {
-            return super.getStatementWriter();            
+            return super.getStatementWriter();
         }
     }
     
@@ -166,7 +162,7 @@ public class StaticTypesWriterController extends DelegatingController {
     @Override
     public BinaryExpressionHelper getBinaryExpressionHelper() {
         if (isInStaticallyCheckedMethod) {
-            return binaryExprHelper;
+            return binaryExpressionHelper;
         } else {
             return super.getBinaryExpressionHelper();
         }
diff --git a/subprojects/groovy-templates/src/main/groovy/groovy/text/markup/MarkupTemplateEngine.java b/subprojects/groovy-templates/src/main/groovy/groovy/text/markup/MarkupTemplateEngine.java
index 7e9b220097..1c23437def 100644
--- a/subprojects/groovy-templates/src/main/groovy/groovy/text/markup/MarkupTemplateEngine.java
+++ b/subprojects/groovy-templates/src/main/groovy/groovy/text/markup/MarkupTemplateEngine.java
@@ -23,7 +23,7 @@ import groovy.lang.GroovyCodeSource;
 import groovy.lang.Writable;
 import groovy.text.Template;
 import groovy.text.TemplateEngine;
-import groovy.transform.CompileStatic;
+import groovy.transform.TypeChecked;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.classgen.GeneratorContext;
@@ -47,6 +47,7 @@ import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
@@ -63,7 +64,7 @@ public class MarkupTemplateEngine extends TemplateEngine {
 
     private static final Pattern LOCALIZED_RESOURCE_PATTERN = Pattern.compile("(.+?)(?:_([a-z]{2}(?:_[A-Z]{2,3})))?\\.([\\p{Alnum}.]+)$");
 
-    private static final boolean DEBUG_BYTECODE = Boolean.valueOf(System.getProperty("markuptemplateengine.compiler.debug","false"));
+    private static final boolean DEBUG_BYTECODE = Boolean.getBoolean("markuptemplateengine.compiler.debug");
 
     private static final AtomicLong counter = new AtomicLong();
 
@@ -77,35 +78,36 @@ public class MarkupTemplateEngine extends TemplateEngine {
         this(new TemplateConfiguration());
     }
 
-    public MarkupTemplateEngine(final TemplateConfiguration tplConfig) {
-        this(MarkupTemplateEngine.class.getClassLoader(), tplConfig);
+    public MarkupTemplateEngine(final TemplateConfiguration config) {
+        this(MarkupTemplateEngine.class.getClassLoader(), config, null);
     }
 
-    public MarkupTemplateEngine(final ClassLoader parentLoader, final TemplateConfiguration tplConfig) {
-        this(parentLoader, tplConfig, null);
+    public MarkupTemplateEngine(final ClassLoader parentLoader, final TemplateConfiguration config) {
+        this(parentLoader, config, null);
     }
 
-    public MarkupTemplateEngine(final ClassLoader parentLoader, final TemplateConfiguration tplConfig, final TemplateResolver resolver) {
+    public MarkupTemplateEngine(final ClassLoader parentLoader, final TemplateConfiguration config, final TemplateResolver resolver) {
+        templateConfiguration = config;
         compilerConfiguration = new CompilerConfiguration();
-        templateConfiguration = tplConfig;
-        compilerConfiguration.addCompilationCustomizers(new TemplateASTTransformer(tplConfig));
-        compilerConfiguration.addCompilationCustomizers(
-                new ASTTransformationCustomizer(Collections.singletonMap("extensions", "groovy.text.markup.MarkupTemplateTypeCheckingExtension"), CompileStatic.class));
+        List<CompilationCustomizer> customizers = compilerConfiguration.getCompilationCustomizers();
+        customizers.add(new TemplateASTTransformer(templateConfiguration));
+        customizers.add(new ASTTransformationCustomizer(
+                Collections.singletonMap("extensions", "groovy.text.markup.MarkupTemplateTypeCheckingExtension"), TypeChecked.class));
         if (templateConfiguration.isAutoNewLine()) {
-            compilerConfiguration.addCompilationCustomizers(
-                    new CompilationCustomizer(CompilePhase.CONVERSION) {
-                        @Override
-                        public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException {
-                            new AutoNewLineTransformer(source).visitClass(classNode);
-                        }
-                    }
-            );
+            customizers.add(new CompilationCustomizer(CompilePhase.CONVERSION) {
+                @Override
+                public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) {
+                    new AutoNewLineTransformer(source).visitClass(classNode);
+                }
+            });
         }
-        groovyClassLoader = AccessController.doPrivileged((PrivilegedAction<TemplateGroovyClassLoader>) () -> new TemplateGroovyClassLoader(parentLoader, compilerConfiguration));
         if (DEBUG_BYTECODE) {
             compilerConfiguration.setBytecodePostprocessor(BytecodeDumper.STANDARD_ERR);
         }
-        templateResolver = resolver == null ? new DefaultTemplateResolver() : resolver;
+
+        groovyClassLoader = AccessController.doPrivileged((PrivilegedAction<TemplateGroovyClassLoader>) () -> new TemplateGroovyClassLoader(parentLoader, compilerConfiguration));
+
+        templateResolver = resolver != null ? resolver : new DefaultTemplateResolver();
         templateResolver.configure(groovyClassLoader, templateConfiguration);
     }
 
@@ -371,5 +373,4 @@ public class MarkupTemplateEngine extends TemplateEngine {
             return url;
         }
     }
-
 }
diff --git a/subprojects/groovy-templates/src/test/groovy/groovy/text/MarkupTemplateEngineTest.groovy b/subprojects/groovy-templates/src/test/groovy/groovy/text/MarkupTemplateEngineTest.groovy
index 489c59195d..afc28b9221 100644
--- a/subprojects/groovy-templates/src/test/groovy/groovy/text/MarkupTemplateEngineTest.groovy
+++ b/subprojects/groovy-templates/src/test/groovy/groovy/text/MarkupTemplateEngineTest.groovy
@@ -18,7 +18,6 @@
  */
 package groovy.text
 
-import groovy.test.NotYetImplemented
 import groovy.text.markup.BaseTemplate
 import groovy.text.markup.MarkupTemplateEngine
 import groovy.text.markup.TagLibAdapter
@@ -47,93 +46,99 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testSimpleTemplate() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             html {
                 body {
                     yield 'It works!'
                 }
             }
-            '''
+        '''
         String rendered = template.make()
         assert rendered == '<html><body>It works!</body></html>'
     }
 
+    @Test // GROOVY-10731
+    void testSimpleTemplate2() {
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def template = engine.createTemplate '''
+            def map = [:]
+            String key = ""
+            def value = map[key] // ArrayIndexOutOfBoundsException
+            span(class: "label label-${value}")
+        '''
+        String rendered = template.make()
+        assert rendered == '<span class=\'label label-null\'/>'
+    }
+
     @Test
     void testSimpleTemplateWithModel() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             html {
                 body {
                     yield message
                 }
             }
-            '''
-        def model = [message: 'It works!']
-        StringWriter rendered = new StringWriter()
-        template.make(model).writeTo(rendered)
-        assert rendered.toString() == '<html><body>It works!</body></html>'
+        '''
+        String rendered = template.make(message: 'It works!')
+        assert rendered == '<html><body>It works!</body></html>'
     }
 
     @Test
     void testSimpleTemplateWithIncludeTemplate() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             html {
                 body {
                     include template:'includes/hello.tpl'
                 }
             }
-            '''
-        StringWriter rendered = new StringWriter()
-        template.make().writeTo(rendered)
-        assert rendered.toString() == '<html><body>Hello from include!</body></html>'
+        '''
+        String rendered = template.make()
+        assert rendered == '<html><body>Hello from include!</body></html>'
     }
 
     @Test
     void testSimpleTemplateWithIncludeTemplateWithLocale() {
-        def tplConfig = new TemplateConfiguration()
-        tplConfig.locale = Locale.FRANCE
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, tplConfig)
+        def config = new TemplateConfiguration(locale: Locale.FRANCE)
+        def engine = new MarkupTemplateEngine(config)
         def template = engine.createTemplate '''
             html {
                 body {
                     include template:'includes/hello.tpl'
                 }
             }
-            '''
-        StringWriter rendered = new StringWriter()
-        template.make().writeTo(rendered)
-        assert rendered.toString() == '<html><body>Bonjour!</body></html>'
+        '''
+        String rendered = template.make()
+        assert rendered == '<html><body>Bonjour!</body></html>'
     }
 
     @Test
     void testSimpleTemplateWithIncludeTemplateWithLocalePriority() {
-        def tplConfig = new TemplateConfiguration()
-        tplConfig.locale = Locale.FRANCE // set default locale
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, tplConfig)
+        def config = new TemplateConfiguration(locale: Locale.FRANCE)
+        def engine = new MarkupTemplateEngine(config)
         def template = engine.createTemplate '''
             html {
                 body {
                     include template:'includes/hello_en_US.tpl' // if not found, will fall back to the default locale
                 }
             }
-            '''
-        StringWriter rendered = new StringWriter()
-        template.make().writeTo(rendered)
-        assert rendered.toString() == '<html><body>Bonjour!</body></html>'
+        '''
+        String rendered = template.make()
+        assert rendered == '<html><body>Bonjour!</body></html>'
     }
 
     @Test
     void testSimpleTemplateWithIncludeRaw() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             html {
                 body {
                     include unescaped:'includes/hello.html'
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html><body>Hello unescaped!</body></html>'
@@ -141,14 +146,14 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testSimpleTemplateWithIncludeEscaped() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             html {
                 body {
                     include escaped:'includes/hello-escaped.txt'
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html><body>Hello &lt;escaped&gt;!</body></html>'
@@ -156,13 +161,13 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testHTMLHeader() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             yieldUnescaped '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
             html {
                 body('Hello, XHTML!')
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body>Hello, XHTML!</body></html>'
@@ -170,7 +175,7 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testTemplateWithHelperMethod() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             def foo = {
                 body('Hello from foo!')
@@ -179,7 +184,7 @@ final class MarkupTemplateEngineTest {
             html {
                 foo()
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html><body>Hello from foo!</body></html>'
@@ -187,13 +192,13 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testCallPi() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             pi("xml-stylesheet":[href:"mystyle.css", type:"text/css"])
             html {
                 body('Hello, PI!')
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString().normalize() == '<?xml-stylesheet href=\'mystyle.css\' type=\'text/css\'?>\n<html><body>Hello, PI!</body></html>'
@@ -201,13 +206,13 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testXmlDeclaration() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             xmlDeclaration()
             html {
                 body('Hello, PI!')
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString().normalize() == '<?xml version=\'1.0\'?>\n<html><body>Hello, PI!</body></html>'
@@ -215,15 +220,14 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testXmlDeclarationWithEncoding() {
-        def configuration = new TemplateConfiguration()
-        configuration.declarationEncoding = 'UTF-8'
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(configuration)
+        def config = new TemplateConfiguration(declarationEncoding: 'UTF-8')
+        def engine = new MarkupTemplateEngine(config)
         def template = engine.createTemplate '''
             xmlDeclaration()
             html {
                 body('Hello, PI!')
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString().normalize() == '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<html><body>Hello, PI!</body></html>'
@@ -231,14 +235,14 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testNewLine() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         engine.templateConfiguration.newLineString = '||'
         def template = engine.createTemplate '''
             html {
                 newLine()
                 body('Hello, PI!')
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html>||<body>Hello, PI!</body></html>'
@@ -246,10 +250,10 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testXMLWithYieldTag() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             ':yield'()
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<yield/>'
@@ -257,13 +261,13 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testTagsWithAttributes() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             html {
                 a(href:'foo.html', 'Link text')
                 tagWithQuote(attr:"fo'o")
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html><a href=\'foo.html\'>Link text</a><tagWithQuote attr=\'fo&apos;o\'/></html>'
@@ -271,14 +275,14 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testTagsWithAttributesAndDoubleQuotes() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         engine.templateConfiguration.useDoubleQuotes = true
         def template = engine.createTemplate '''
             html {
                 a(href:'foo.html', 'Link text')
                 tagWithQuote(attr:"fo'o")
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html><a href="foo.html">Link text</a><tagWithQuote attr="fo\'o"/></html>'
@@ -286,7 +290,7 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testLoopInTemplate() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def model = [text: 'Hello', persons: ['Bob', 'Alice']]
         def template = engine.createTemplate '''
             html {
@@ -298,7 +302,7 @@ final class MarkupTemplateEngineTest {
                     }
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
         assert rendered.toString() == '<html><body><ul><li>Hello Bob</li><li>Hello Alice</li></ul></body></html>'
@@ -306,7 +310,7 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testHelperFunctionInBinding() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def model = [text: { it.toUpperCase() }]
         def template = engine.createTemplate '''
             html {
@@ -314,7 +318,7 @@ final class MarkupTemplateEngineTest {
                     text('hello')
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
         assert rendered.toString() == '<html><body>HELLO</body></html>'
@@ -322,13 +326,13 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testShouldNotEscapeUserInputAutomatically() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def model = [text: '<xml>']
         def template = engine.createTemplate '''
             html {
                 body(text)
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
         assert rendered.toString() == '<html><body><xml></body></html>'
@@ -336,15 +340,14 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testShouldEscapeUserInputAutomatically() {
-        TemplateConfiguration config = new TemplateConfiguration()
-        config.autoEscape = true
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
+        def config = new TemplateConfiguration(autoEscape: true)
+        def engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def model = [text: '<xml>']
         def template = engine.createTemplate '''
             html {
                 body(text)
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
         assert rendered.toString() == '<html><body>&lt;xml&gt;</body></html>'
@@ -352,15 +355,14 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testShouldNotEscapeUserInputAutomaticallyEvenIfFlagSet() {
-        TemplateConfiguration config = new TemplateConfiguration()
-        config.autoEscape = true
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
+        def config = new TemplateConfiguration(autoEscape: true)
+        def engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def model = [text: '<xml>']
         def template = engine.createTemplate '''
             html {
                 body(unescaped.text)
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
         assert rendered.toString() == '<html><body><xml></body></html>'
@@ -368,12 +370,12 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testTypeCheckedModel() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTypeCheckedModelTemplate '''
             html {
                 body(text.toUpperCase())
             }
-            ''', [text: 'String']
+        ''', [text: 'String']
         def model = [text: 'Type checked!']
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
@@ -383,12 +385,12 @@ final class MarkupTemplateEngineTest {
     @Test
     void testTypeCheckedModelShouldFail() {
         def err = shouldFail {
-            MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+            def engine = new MarkupTemplateEngine(new TemplateConfiguration())
             def template = engine.createTypeCheckedModelTemplate '''
                 html {
                     body(text.toUpperCase())
                 }
-                ''', [text: 'Integer']
+            ''', [text: 'Integer']
             def model = [text: 'Type checked!']
             StringWriter rendered = new StringWriter()
             template.make(model).writeTo(rendered)
@@ -401,28 +403,27 @@ final class MarkupTemplateEngineTest {
     @Test
     void testTypeCheckedModelShouldFailWithoutModelDescription() {
         def err = shouldFail {
-            MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+            def engine = new MarkupTemplateEngine(new TemplateConfiguration())
             def template = engine.createTypeCheckedModelTemplate '''
                 html {
                     body(p.name.toUpperCase())
                 }
-                ''', [:]
+            ''', [:]
             def model = [p: new Person(name: 'Cédric')]
             StringWriter rendered = new StringWriter()
             template.make(model).writeTo(rendered)
         }
-
         assert err =~ /No such property: name/
     }
 
     @Test
     void testTypeCheckedModelShouldSucceedWithModelDescription() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTypeCheckedModelTemplate '''
             html {
                 body(p.name.toUpperCase())
             }
-            ''', [p: 'groovy.text.MarkupTemplateEngineTest.Person']
+        ''', [p: 'groovy.text.MarkupTemplateEngineTest.Person']
         def model = [p: new Person(name: 'Cedric')]
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
@@ -431,7 +432,7 @@ final class MarkupTemplateEngineTest {
 
     @Test
     void testTypeCheckedModelShouldSucceedWithModelDescriptionUsingGenerics() {
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+        def engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTypeCheckedModelTemplate '''
             html {
                 ul {
@@ -440,7 +441,7 @@ final class MarkupTemplateEngineTest {
                     }
                 }
             }
-            ''', [persons: 'List<groovy.text.MarkupTemplateEngineTest.Person>']
+        ''', [persons: 'List<groovy.text.MarkupTemplateEngineTest.Person>']
         def model = [persons: [new Person(name: 'Cedric')]]
         StringWriter rendered = new StringWriter()
         template.make(model).writeTo(rendered)
@@ -450,28 +451,25 @@ final class MarkupTemplateEngineTest {
     @Test
     void testTypeCheckedTemplateShouldFailInInclude() {
         def err = shouldFail {
-            MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
+            def engine = new MarkupTemplateEngine(new TemplateConfiguration())
             def template = engine.createTypeCheckedModelTemplate '''
                 html {
                     body {
                         include template:'includes/typecheckedinclude.tpl'
                     }
                 }
-                ''', [text: 'Integer']
+            ''', [text: 'Integer']
             def model = [text: 'Type checked!']
             StringWriter rendered = new StringWriter()
             template.make(model).writeTo(rendered)
         }
-
         assert err =~ /Cannot find matching method java.lang.Integer#toUpperCase\(\)/
     }
 
     @Test
     void testSimpleAutoIndent() {
-        TemplateConfiguration config = new TemplateConfiguration()
-        config.autoIndent = true
-        config.newLineString = '\n'
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
+        def config = new TemplateConfiguration(autoIndent: true, newLineString: '\n')
+        def engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def template = engine.createTemplate '''
 html {
     newLine()
@@ -495,10 +493,8 @@ html {
 
     @Test
     void testSimpleAutoIndentShouldAddNewLineInLoop() {
-        TemplateConfiguration config = new TemplateConfiguration()
-        config.autoIndent = true
-        config.newLineString = '\n'
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
+        def config = new TemplateConfiguration(autoIndent: true, newLineString: '\n')
+        def engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def template = engine.createTemplate '''
 html {
     newLine()
@@ -533,11 +529,8 @@ html {
 
     @Test
     void testSimpleAutoIndentShouldAutoAddNewLineInLoop() {
-        TemplateConfiguration config = new TemplateConfiguration()
-        config.autoIndent = true
-        config.autoNewLine = true
-        config.newLineString = '\n'
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
+        def config = new TemplateConfiguration(autoIndent: true, autoNewLine: true, newLineString: '\n')
+        def engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def template = engine.createTemplate '''
 html {
     body {
@@ -567,11 +560,8 @@ html {
 
     @Test
     void testSimpleAutoIndentWithAutoNewLine() {
-        TemplateConfiguration config = new TemplateConfiguration()
-        config.autoIndent = true
-        config.autoNewLine = true
-        config.newLineString = '\n'
-        MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
+        def config = new TemplateConfiguration(autoIndent: true, autoNewLine: true, newLineString: '\n')
+        def engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def template = engine.createTemplate '''
 html {
     body {
@@ -608,7 +598,7 @@ html {
         MarkupTemplateEngine engine = new MarkupTemplateEngine(this.class.classLoader, config)
         def template = engine.createTemplate '''int x = name.length()
             yield "$name: $x"
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         def model = [name: 'Michel']
         def tpl = template.make(model)
@@ -670,7 +660,7 @@ html {
                         include template:'hello-from-dir.tpl'
                     }
                 }
-                '''
+            '''
             StringWriter rendered = new StringWriter()
             template.make().writeTo(rendered)
             assert rendered.toString() == '<html><body>Hello from include!</body></html>'
@@ -707,16 +697,13 @@ html {
     @Test
     void testTypeCheckedModelShouldNotConflictWithAutoEscape() {
         def model = [title: "This is my glorious title ${1 + 1}".toString()]
-        def template = new MarkupTemplateEngine(
-                getClass().getClassLoader(),
-                new TemplateConfiguration(autoNewLine: true, autoEscape: true, newLineString: 'NL')).createTypeCheckedModelTemplate('''
-                    body {
-                      div(class: 'text')  {
-                        yield title.toUpperCase()
-                      }
-                    }
-
-                    ''', [title: 'String'])
+        def template = new MarkupTemplateEngine(this.class.classLoader, new TemplateConfiguration(autoNewLine: true, autoEscape: true, newLineString: 'NL')).createTypeCheckedModelTemplate('''
+            body {
+              div(class: 'text')  {
+                yield title.toUpperCase()
+              }
+            }
+        ''', [title: 'String'])
         def stringWriter = new StringWriter()
         template.make(model).writeTo(stringWriter)
         assert stringWriter.toString() == '<body>NL<div class=\'text\'>NLTHIS IS MY GLORIOUS TITLE 2NL</div>NL</body>'
@@ -756,7 +743,7 @@ html {
                     }
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         def model = [persons: [[name: 'Cedric'], [name: 'Jochen']]]
         template.make(model).writeTo(rendered)
@@ -780,7 +767,7 @@ html {
                     }
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         def model = [persons: [[name: 'Cedric'], [name: 'Jochen']]]
         template.make(model).writeTo(rendered)
@@ -805,13 +792,12 @@ html {
                         }
                     }
                 }
-                '''
+            '''
             StringWriter rendered = new StringWriter()
             def model = [persons: [[name: 'Cedric'], [name: 'Jochen']]]
             template.make(model).writeTo(rendered)
             assert rendered.toString() == '<html><body><ul><li>Cedric</li><li>Jochen</li></ul></body></html>'
         }
-
         assert err =~ /No such property: name for class: java.lang.String/
     }
 
@@ -829,7 +815,7 @@ html {
                     }
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == "<html><body><a href='index.html'>Index</a><a href='page.html'>Page1</a><a href='page2.html'>Page2</a></body></html>"
@@ -845,7 +831,7 @@ html {
                     p('This is the body')
                 }
             }, title: 'This is the title'
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == "<html><head><title>This is the title</title></head><body><div><p>This is the body</p></div></body></html>"
@@ -861,7 +847,7 @@ html {
                     p('This is the body')
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make([title:'This is the title']).writeTo(rendered)
         assert rendered.toString() == "<html><head><title/></head><body><div><p>This is the body</p></div></body></html>"
@@ -872,7 +858,7 @@ html {
                     p('This is the body')
                 }
             }
-            '''
+        '''
         rendered = new StringWriter()
         template.make([title:'This is the title']).writeTo(rendered)
         assert rendered.toString() == "<html><head><title>This is the title</title></head><body><div><p>This is the body</p></div></body></html>"
@@ -883,7 +869,7 @@ html {
                     p('This is the body')
                 }
             }, title: 'This is another title'
-            '''
+        '''
         rendered = new StringWriter()
         template.make([title:'This is the title']).writeTo(rendered)
         assert rendered.toString() == "<html><head><title>This is another title</title></head><body><div><p>This is the body</p></div></body></html>"
@@ -907,7 +893,7 @@ html {
         def template = engine.createTemplate '''messages.each { message ->
                 yield message.summary
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         def model = [messages: [new Message(summary: 'summary')]]
         template.make(model).writeTo(rendered)
@@ -940,7 +926,7 @@ html {
                     include template:'includes/hello.tpl'
                 }
             }
-            '''
+        '''
         StringWriter rendered = new StringWriter()
         template.make().writeTo(rendered)
         assert rendered.toString() == '<html><body>Hello from include!Hello from include!Hello from include!</body></html>'
@@ -960,7 +946,7 @@ html {
                     p("This is a p with ${false?$a(href:'link.html','link'):x}")
                 }
             }
-            '''
+        '''
         String rendered = template.make()
         assert rendered == '<html><body><p>This is a p with <a href=\'link.html\'>link</a></p><p>This is a p with directly <p>hg</p></p></body></html>'
     }
@@ -977,7 +963,7 @@ html {
                     p("This is a p with ${stringOf { false?a(href:'link.html','link'):x} }")
                 }
             }
-            '''
+        '''
         String rendered = template.make()
         assert rendered == '<html><body><p>This is a p with <a href=\'link.html\'>link</a></p><p>This is a p with directly <p>hg</p></p></body></html>'
     }
@@ -987,7 +973,7 @@ html {
         MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             p("This is an ${strong('error')}")
-            '''
+        '''
         String rendered = template.make().writeTo(new StringWriter())
         assert rendered == '<strong>error</strong><p>This is an </p>'
     }
@@ -1010,12 +996,12 @@ html {
         assert rendered == '<a>link</a>'
     }
 
-    @Test @NotYetImplemented // GROOVY-6939
+    @Test // GROOVY-6939
     void testShouldNotFailWithDoCallMethod() {
         MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             groups.each { k, v -> li(k) }
-            '''
+        '''
         def model = [groups:[a:'Group a',b:'Group b']]
         String rendered = template.make(model)
         assert rendered == '<li>a</li><li>b</li>'
@@ -1026,7 +1012,7 @@ html {
         MarkupTemplateEngine engine = new MarkupTemplateEngine(new TemplateConfiguration())
         def template = engine.createTemplate '''
             yield list[0]
-            '''
+        '''
         def model = [list:['Item 1']]
         String rendered = template.make(model)
         assert rendered == 'Item 1'
@@ -1034,7 +1020,7 @@ html {
         template = engine.createTemplate '''
             list[0] = 'Item 2'
             yield list[0]
-            '''
+        '''
         model = [list:['Item 1']]
         rendered = template.make(model)
         assert model.list[0] == 'Item 2'
@@ -1044,7 +1030,7 @@ html {
             def indirect = list
             indirect[0] = 'Item 4'
             yield list[0]
-            '''
+        '''
         model = [list:['Item 3']]
         rendered = template.make(model)
         assert model.list[0] == 'Item 4'
@@ -1058,12 +1044,14 @@ html {
             div {
                 yield xml.file.name
             }
-            '''
+        '''
         def model = [xml: [file:[name:'test']]]
         String rendered = template.make(model)
         assert rendered == '<div>test</div>'
     }
 
+    //--------------------------------------------------------------------------
+
     static class SimpleTagLib {
         def emoticon = { attrs, body ->
             out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")