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/11/30 21:00:28 UTC

[groovy] branch master updated: streamline `GroovyStarter` -> `Groovysh` -> user's script

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

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


The following commit(s) were added to refs/heads/master by this push:
     new a4dc3faef6 streamline `GroovyStarter` -> `Groovysh` -> user's script
a4dc3faef6 is described below

commit a4dc3faef6ef4588f0cb32f7d2d8e6bf292d10cd
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Nov 30 14:59:38 2022 -0600

    streamline `GroovyStarter` -> `Groovysh` -> user's script
---
 .../java/org/codehaus/groovy/ast/ModuleNode.java   |  17 +-
 .../org/codehaus/groovy/tools/GroovyStarter.java   |  48 ++-
 .../codehaus/groovy/tools/LoaderConfiguration.java |   3 +-
 .../java/org/codehaus/groovy/tools/RootLoader.java |  94 +++---
 .../transform/BaseScriptASTTransformation.java     |  17 +-
 .../org/apache/groovy/groovysh/Groovysh.groovy     | 206 ++++++------
 .../groovy/groovysh/InteractiveShellRunner.groovy  |  77 +++--
 .../org/apache/groovy/groovysh/Interpreter.groovy  |  73 +++--
 .../groovy/org/apache/groovy/groovysh/Main.groovy  |  17 +-
 .../groovysh/util/DefaultCommandsRegistrar.groovy  |  15 +-
 .../groovy/groovysh/CompleterTestSupport.groovy    |  37 +--
 .../groovy/groovysh/ImportCompleterTest.groovy     |   5 +-
 .../groovy/groovysh/ShellRunnerTestSupport.groovy  |  30 +-
 .../completion/GroovySyntaxCompleterTest.groovy    | 351 ++++++++-------------
 14 files changed, 428 insertions(+), 562 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/ModuleNode.java b/src/main/java/org/codehaus/groovy/ast/ModuleNode.java
index ba0dbb120b..b56c28fd32 100644
--- a/src/main/java/org/codehaus/groovy/ast/ModuleNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ModuleNode.java
@@ -18,7 +18,6 @@
  */
 package org.codehaus.groovy.ast;
 
-import groovy.lang.Binding;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.classgen.GeneratorContext;
@@ -41,13 +40,11 @@ import java.util.stream.Collectors;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorSuperX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
-import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
-import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
 import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
 import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
@@ -377,17 +374,13 @@ public class ModuleNode extends ASTNode {
         // In practice this will always be true because currently this visitor is run before the AST transformations
         // (like @BaseScript) that could change this.  But this is cautious and anticipates possible compiler changes.
         if (classNode.getSuperClass().getDeclaredConstructor(params(param(ClassHelper.BINDING_TYPE, "context"))) != null) {
-            stmt = stmt(ctorX(ClassNode.SUPER, args(varX("context"))));
+            stmt = stmt(ctorSuperX(args(varX("context"))));
         } else {
             // Fallback for non-standard base "script" classes with no context (Binding) constructor.
             stmt = stmt(callX(varX("super"), "setBinding", args(varX("context"))));
         }
 
-        classNode.addConstructor(
-            ACC_PUBLIC,
-            finalParam(ClassHelper.make(Binding.class), "context"),
-            ClassNode.EMPTY_ARRAY,
-            stmt);
+        classNode.addConstructor(ACC_PUBLIC, finalParam(ClassHelper.BINDING_TYPE, "context"), ClassNode.EMPTY_ARRAY, stmt);
 
         for (MethodNode method : methods) {
             if (method.isAbstract()) {
@@ -413,8 +406,8 @@ public class ModuleNode extends ASTNode {
                     ClassNode argType = node.getParameters()[0].getType();
                     ClassNode retType = node.getReturnType();
 
-                    argTypeMatches = (isObjectType(argType) || argType.getName().contains("String[]"));
-                    retTypeMatches = (isPrimitiveVoid(retType) || isObjectType(retType));
+                    argTypeMatches = (ClassHelper.isObjectType(argType) || argType.getName().contains("String[]"));
+                    retTypeMatches = (ClassHelper.isPrimitiveVoid(retType) || ClassHelper.isObjectType(retType));
                     if (retTypeMatches && argTypeMatches) {
                         if (found) {
                             throw new RuntimeException("Repetitive main method found.");
diff --git a/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java b/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
index 2dd9a596ab..fdd3491cca 100644
--- a/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
+++ b/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
@@ -19,9 +19,8 @@
 package org.codehaus.groovy.tools;
 
 import java.io.FileInputStream;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.security.PrivilegedAction;
+import java.util.Arrays;
 
 /**
  * Helper class to initialize the Groovy runtime.
@@ -33,6 +32,14 @@ public class GroovyStarter {
         System.exit(1);
     }
 
+    public static void main(String[] args) {
+        try {
+            rootLoader(args);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
     public static void rootLoader(String[] args) {
         String conf = System.getProperty("groovy.starter.conf",null);
         final LoaderConfiguration lc = new LoaderConfiguration();
@@ -72,7 +79,7 @@ public class GroovyStarter {
                     break;
                 default:
                     break label;
-            }            
+            }
         }
 
         // this allows to override the commandline conf
@@ -84,9 +91,8 @@ public class GroovyStarter {
             exit("no configuration file or main class specified");
         }
 
-        // copy arguments for main class 
-        String[] newArgs = new String[args.length-argsOffset];
-        System.arraycopy(args, 0 + argsOffset, newArgs, 0, newArgs.length);
+        // copy arguments for main class
+        String[] mainArgs = Arrays.copyOfRange(args, argsOffset, args.length);
         // load configuration file
         if (conf!=null) {
             try {
@@ -98,23 +104,23 @@ public class GroovyStarter {
         }
         // create loader and execute main class
         ClassLoader loader = getLoader(lc);
-        Method m=null;
+        Method m = null;
         try {
-            Class c = loader.loadClass(lc.getMainClass());
+            Class<?> c = loader.loadClass(lc.getMainClass());
             m = c.getMethod("main", String[].class);
-        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e1) {
-            exit(e1);
+        } catch (ReflectiveOperationException | SecurityException e2) {
+            exit(e2);
         }
         try {
-            m.invoke(null, new Object[]{newArgs});
-        } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e3) {
+            m.invoke(null, new Object[]{mainArgs});
+        } catch (ReflectiveOperationException | IllegalArgumentException e3) {
             exit(e3);
         }
     }
 
-    @SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action
-    private static RootLoader getLoader(LoaderConfiguration lc) {
-        return java.security.AccessController.doPrivileged((PrivilegedAction<RootLoader>) () -> new RootLoader(lc));
+    @SuppressWarnings("removal") // TODO: a future Groovy version should perform the operation not as a privileged action
+    private static ClassLoader getLoader(LoaderConfiguration lc) {
+        return java.security.AccessController.doPrivileged((java.security.PrivilegedAction<ClassLoader>) () -> new RootLoader(lc));
     }
 
     private static void exit(Exception e) {
@@ -122,16 +128,8 @@ public class GroovyStarter {
         System.exit(1);
     }
 
-    private static void exit(String msg) {
-        System.err.println(msg);
+    private static void exit(String text) {
+        System.err.println(text);
         System.exit(1);
     }
-
-    public static void main(String[] args) {
-        try {
-            rootLoader(args);
-        } catch (Throwable t) {
-            t.printStackTrace();
-        }
-    }
 }
diff --git a/src/main/java/org/codehaus/groovy/tools/LoaderConfiguration.java b/src/main/java/org/codehaus/groovy/tools/LoaderConfiguration.java
index 27daeea92f..4acf272ce8 100644
--- a/src/main/java/org/codehaus/groovy/tools/LoaderConfiguration.java
+++ b/src/main/java/org/codehaus/groovy/tools/LoaderConfiguration.java
@@ -19,7 +19,6 @@
 package org.codehaus.groovy.tools;
 
 import org.apache.groovy.util.SystemUtil;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -142,7 +141,7 @@ public class LoaderConfiguration {
 
         if (requireMain && main == null) throw new IOException("missing main class definition in config file");
         if (!configScripts.isEmpty()) {
-            System.setProperty("groovy.starter.configscripts", DefaultGroovyMethods.join((Iterable)configScripts, ","));
+            System.setProperty("groovy.starter.configscripts", String.join(",", configScripts));
         }
     }
 
diff --git a/src/main/java/org/codehaus/groovy/tools/RootLoader.java b/src/main/java/org/codehaus/groovy/tools/RootLoader.java
index a47508fd46..ff8779c430 100644
--- a/src/main/java/org/codehaus/groovy/tools/RootLoader.java
+++ b/src/main/java/org/codehaus/groovy/tools/RootLoader.java
@@ -23,8 +23,8 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * This ClassLoader should be used as root of class loaders. Any
@@ -73,58 +73,54 @@ import java.util.Map;
  * instance will solve that problem.
  */
 public class RootLoader extends URLClassLoader {
-    private static final URL[] EMPTY_URL_ARRAY = new URL[0];
-    private final Map<String, Class> customClasses = new HashMap<>();
+
     private static final String ORG_W3C_DOM_NODE = "org.w3c.dom.Node";
+    private final Map<String, Class<?>> customClasses = new HashMap<>();
 
     /**
-     * constructs a new RootLoader without classpath
+     * Constructs a {@code RootLoader} without classpath.
      *
      * @param parent the parent Loader
      */
-    public RootLoader(ClassLoader parent) {
-        this(EMPTY_URL_ARRAY, parent);
+    public RootLoader(final ClassLoader parent) {
+        this(new URL[0], parent);
     }
 
     /**
-     * constructs a new RootLoader with a parent loader and an
-     * array of URLs as classpath
+     * Constructs a {@code RootLoader} with a parent loader and an array of URLs
+     * as its classpath.
      */
-    public RootLoader(URL[] urls, ClassLoader parent) {
+    public RootLoader(final URL[] urls, final ClassLoader parent) {
         super(urls, parent);
-        // major hack here...!
+        // major hack here!!
         try {
             customClasses.put(ORG_W3C_DOM_NODE, super.loadClass(ORG_W3C_DOM_NODE, false));
-        } catch (Exception e) { /* ignore */ }
-    }
-
-    private static ClassLoader chooseParent() {
-        ClassLoader cl = RootLoader.class.getClassLoader();
-        if (cl != null) return cl;
-        return ClassLoader.getSystemClassLoader();
+        } catch (Exception e) {
+            // ignore
+        }
     }
 
     /**
-     * constructs a new RootLoader with a {@link LoaderConfiguration}
-     * object which holds the classpath
+     * Constructs a {@code RootLoader} with a {@link LoaderConfiguration} object
+     * which holds the classpath.
      */
-    public RootLoader(LoaderConfiguration lc) {
-        this(chooseParent());
+    public RootLoader(final LoaderConfiguration lc) {
+        this(Optional.ofNullable(RootLoader.class.getClassLoader()).orElseGet(ClassLoader::getSystemClassLoader));
+
         Thread.currentThread().setContextClassLoader(this);
-        URL[] urls = lc.getClassPathUrls();
-        for (URL url : urls) {
+
+        for (URL url : lc.getClassPathUrls()) {
             addURL(url);
         }
         // TODO M12N eventually defer this until later when we have a full Groovy
         // environment and use normal Grape.grab()
         String groovyHome = System.getProperty("groovy.home");
-        List<String> grabUrls = lc.getGrabUrls();
-        for (String grabUrl : grabUrls) {
-            Map<String, Object> grabParts = GrapeUtil.getIvyParts(grabUrl);
-            String group = grabParts.get("group").toString();
-            String module = grabParts.get("module").toString();
-            String name = grabParts.get("module").toString() + "-" + grabParts.get("version") + ".jar";
-            File jar = new File(groovyHome + "/repo/" + group + "/" + module + "/jars/" + name);
+        for (String url : lc.getGrabUrls()) {
+            Map<String, Object> grabParts = GrapeUtil.getIvyParts(url);
+            String group   = (String) grabParts.get("group");
+            String module  = (String) grabParts.get("module");
+            String version = (String) grabParts.get("version");
+            File jar = new File(groovyHome + "/repo/" + group + "/" + module + "/jars/" + module + "-" + version + ".jar");
             try {
                 addURL(jar.toURI().toURL());
             } catch (MalformedURLException e) {
@@ -134,51 +130,53 @@ public class RootLoader extends URLClassLoader {
     }
 
     /**
-     * loads a class using the name of the class
+     * {@inheritDoc}
      */
     @Override
-    protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
-        Class c = this.findLoadedClass(name);
+    protected synchronized Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
+        Class<?> c = findLoadedClass(name);
         if (c != null) return c;
         c = customClasses.get(name);
         if (c != null) return c;
 
         try {
-            c = oldFindClass(name);
-        } catch (ClassNotFoundException cnfe) {
-            // IGNORE
+            c = super.findClass(name);
+        } catch (ClassNotFoundException e) {
+            // ignore
         }
-        if (c == null) c = super.loadClass(name, resolve);
+        if (c == null)
+            c = super.loadClass(name, resolve);
 
-        if (resolve) resolveClass(c);
+        if (resolve)
+            resolveClass(c);
 
         return c;
     }
 
     /**
-     * returns the URL of a resource, or null if it is not found
+     * {@inheritDoc}
      */
     @Override
-    public URL getResource(String name) {
+    public URL getResource(final String name) {
         URL url = findResource(name);
-        if (url == null) url = super.getResource(name);
+        if (url == null)
+            url = super.getResource(name);
         return url;
     }
 
     /**
-     * adds an url to the classpath of this classloader
+     * {@inheritDoc}
      */
     @Override
-    public void addURL(URL url) {
+    public void addURL(final URL url) {
         super.addURL(url);
     }
 
-    private Class oldFindClass(String name) throws ClassNotFoundException {
-        return super.findClass(name);
-    }
-
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    protected Class findClass(String name) throws ClassNotFoundException {
+    protected Class<?> findClass(final String name) throws ClassNotFoundException {
         throw new ClassNotFoundException(name);
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/transform/BaseScriptASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/BaseScriptASTTransformation.java
index 21cca0fa43..470cc3d14e 100644
--- a/src/main/java/org/codehaus/groovy/transform/BaseScriptASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/BaseScriptASTTransformation.java
@@ -24,7 +24,6 @@ import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.ConstructorNode;
 import org.codehaus.groovy.ast.ImportNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.PackageNode;
@@ -51,7 +50,6 @@ public class BaseScriptASTTransformation extends AbstractASTTransformation {
     private static final Class<BaseScript> MY_CLASS = BaseScript.class;
     public static final ClassNode MY_TYPE = ClassHelper.make(MY_CLASS);
     private static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
-    private static final Parameter[] CONTEXT_CTOR_PARAMETERS = {new Parameter(ClassHelper.BINDING_TYPE, "context")};
 
     @Override
     public void visit(ASTNode[] nodes, SourceUnit source) {
@@ -151,16 +149,15 @@ public class BaseScriptASTTransformation extends AbstractASTTransformation {
         // If the new script base class does not have a contextual constructor (g.l.Binding), then we won't either.
         // We have to do things this way (and rely on just default constructors) because the logic that generates
         // the constructors for our script class have already run.
-        if (cNode.getSuperClass().getDeclaredConstructor(CONTEXT_CTOR_PARAMETERS) == null) {
-            ConstructorNode orphanedConstructor = cNode.getDeclaredConstructor(CONTEXT_CTOR_PARAMETERS);
-            cNode.removeConstructor(orphanedConstructor);
+        Parameter[] contextualSignature = {new Parameter(ClassHelper.BINDING_TYPE, "context")};
+        if (cNode.getSuperClass().getDeclaredConstructor(contextualSignature) == null) {
+            cNode.removeConstructor(cNode.getDeclaredConstructor(contextualSignature));
         }
     }
 
-    private static boolean isCustomScriptBodyMethod(MethodNode node) {
-        return node != null
-            && !(node.getDeclaringClass().equals(ClassHelper.SCRIPT_TYPE)
-                && "run".equals(node.getName())
-                && node.getParameters().length == 0);
+    private static boolean isCustomScriptBodyMethod(final MethodNode mn) {
+        return mn != null && !(mn.getName().equals("run")
+                            && mn.getParameters().length == 0
+                            && mn.getDeclaringClass().equals(ClassHelper.SCRIPT_TYPE));
     }
 }
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Groovysh.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Groovysh.groovy
index 2fbcd6548a..8be4ba6795 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Groovysh.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Groovysh.groovy
@@ -18,7 +18,11 @@
  */
 package org.apache.groovy.groovysh
 
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
 import jline.Terminal
 import jline.WindowsTerminal
 import jline.console.history.FileHistory
@@ -44,18 +48,19 @@ import org.fusesource.jansi.AnsiRenderer
 
 import java.util.regex.Pattern
 
+import static org.codehaus.groovy.control.CompilerConfiguration.DEFAULT
+
 /**
  * An interactive shell for evaluating Groovy code from the command-line (aka. groovysh).
  *
  * The set of available commands can be modified by placing a file in the classpath named
  * <code>org/codehaus/groovy/tools/shell/commands.xml</code>
  *
- * See {@link XmlCommandRegistrar}
+ * @see XmlCommandRegistrar
  */
+@AutoFinal @CompileStatic
 class Groovysh extends Shell {
 
-    private static final MessageSource messages = new MessageSource(Groovysh)
-
     private static final Pattern TYPEDEF_PATTERN = ~'^\\s*((?:public|protected|private|static|abstract|final)\\s+)*(?:class|enum|interface).*'
     private static final Pattern METHODDEF_PATTERN = ~'^\\s*((?:public|protected|private|static|abstract|final|synchronized)\\s+)*[a-zA-Z_.]+[a-zA-Z_.<>]+\\s+[a-zA-Z_]+\\(.*'
 
@@ -70,6 +75,9 @@ class Groovysh extends Shell {
     // after how many prefix characters we start displaying all metaclass methods
     public static final String METACLASS_COMPLETION_PREFIX_LENGTH_PREFERENCE_KEY = 'meta-completion-prefix-length'
 
+    private static final MessageSource messages = new MessageSource(Groovysh)
+
+    //
 
     final BufferManager buffers = new BufferManager()
 
@@ -86,59 +94,57 @@ class Groovysh extends Shell {
 
     FileHistory history
 
-    boolean historyFull  // used as a workaround for GROOVY-2177
+    boolean historyFull // used as a workaround for GROOVY-2177
 
-    String evictedLine  // remembers the command which will get evicted if history is full
+    String evictedLine // remembers the command which will get evicted if history is full
 
     PackageHelper packageHelper
+
     private CompilerConfiguration configuration
 
-    Groovysh(final ClassLoader classLoader, final Binding binding, final IO io, final Closure registrar) {
-        this(classLoader, binding, io, registrar, CompilerConfiguration.DEFAULT)
+    private static Closure createDefaultRegistrar(ClassLoader classLoader) {
+        return { Groovysh groovysh ->
+            URL xmlCommandResource = groovysh.getClass().getResource('commands.xml')
+            if (xmlCommandResource != null) {
+                def registrar = new XmlCommandRegistrar(groovysh, classLoader)
+                registrar.register(xmlCommandResource)
+            } else {
+                def registrar = new DefaultCommandsRegistrar(groovysh)
+                registrar.register()
+            }
+        }
     }
 
-    Groovysh(final ClassLoader classLoader, final Binding binding, final IO io, final Closure registrar, CompilerConfiguration configuration) {
-       this(classLoader, binding, io, registrar, configuration,  new Interpreter(classLoader, binding, configuration))
-    }
+    //--------------------------------------------------------------------------
 
-    Groovysh(final ClassLoader classLoader, final Binding binding, final IO io, final Closure registrar, CompilerConfiguration configuration, Interpreter interpreter) {
+    Groovysh(ClassLoader classLoader, Binding binding, IO io, @ClosureParams(value=SimpleType, options='org.apache.groovy.groovysh.Groovysh') Closure registrar, CompilerConfiguration configuration, Interpreter interpreter) {
         super(io)
         assert classLoader
         assert binding
-        def actualRegistrar = registrar ?: createDefaultRegistrar(classLoader)
-        parser = new Parser()
+        def theRegistrar = registrar ?: createDefaultRegistrar(classLoader)
         interp = interpreter
-        actualRegistrar.call(this)
-        this.packageHelper = new PackageHelperImpl(classLoader)
+        parser = new Parser()
+        theRegistrar.call(this)
+        packageHelper = new PackageHelperImpl(classLoader)
         this.configuration = configuration
     }
 
-    private static Closure createDefaultRegistrar(final ClassLoader classLoader) {
-        return {Groovysh shell ->
-            URL xmlCommandResource = getClass().getResource('commands.xml')
-            if (xmlCommandResource != null) {
-                def r = new XmlCommandRegistrar(shell, classLoader)
-                r.register(xmlCommandResource)
-            } else {
-                new DefaultCommandsRegistrar(shell).register()
-            }
-        }
+    Groovysh(ClassLoader classLoader, Binding binding, IO io, @ClosureParams(value=SimpleType, options='org.apache.groovy.groovysh.Groovysh') Closure registrar = null, CompilerConfiguration configuration = DEFAULT) {
+       this(classLoader, binding, io, registrar, configuration, new Interpreter(classLoader, binding, configuration))
     }
 
-    Groovysh(final ClassLoader classLoader, final Binding binding, final IO io) {
-        this(classLoader, binding, io, null)
-    }
+    // ClassLoader,Binding,IO variants (drop left-to-right)
 
-    Groovysh(final Binding binding, final IO io) {
-        this(Thread.currentThread().contextClassLoader, binding, io)
+    Groovysh(Binding binding, IO io) {
+        this(Thread.currentThread().getContextClassLoader(), binding, io)
     }
 
-    Groovysh(final IO io) {
-        this(new Binding(), io)
+    Groovysh(IO io, CompilerConfiguration cc) {
+        this(Thread.currentThread().getContextClassLoader(), new Binding(), io, null, cc)
     }
 
-    Groovysh(final IO io, CompilerConfiguration configuration) {
-        this(Thread.currentThread().contextClassLoader, new Binding(), io, null, configuration)
+    Groovysh(IO io) {
+        this(new Binding(), io)
     }
 
     Groovysh() {
@@ -153,11 +159,11 @@ class Groovysh extends Shell {
      * Execute a single line, where the line may be a command or Groovy code (complete or incomplete).
      */
     @Override
-    Object execute(final String line) {
+    Object execute(String line) {
         assert line != null
 
         // Ignore empty lines
-        if (line.trim().size() == 0) {
+        if (line.trim().isEmpty()) {
             return null
         }
 
@@ -178,7 +184,7 @@ class Groovysh extends Shell {
         }
 
         // Otherwise treat the line as Groovy
-        List<String> current = new ArrayList<String>(buffers.current())
+        List<String> current = new ArrayList<>(buffers.current())
 
         // Append the line to the current buffer
         current << line
@@ -242,7 +248,6 @@ class Groovysh extends Shell {
         return result
     }
 
-    @CompileStatic
     private boolean isIncompleteCaseOfAntlr4(MultipleCompilationErrorsException t) {
         // TODO antlr4 parser errors pop out here - can we rework to be like antlr2?
         (
@@ -259,16 +264,15 @@ class Groovysh extends Shell {
      * @param strings
      * @return
      */
-    @CompileStatic
-    static boolean isTypeOrMethodDeclaration(final List<String> buffer) {
-        final String joined = buffer.join('')
+    static boolean isTypeOrMethodDeclaration(List<String> buffer) {
+        String joined = buffer.join('')
         return joined.matches(TYPEDEF_PATTERN) || joined.matches(METHODDEF_PATTERN)
     }
 /*
      * to simulate an interpreter mode, this method wraps the statements into a try/finally block that
      * stores bound variables like unbound variables
      */
-    private Object evaluateWithStoredBoundVars(String importsSpec, final List<String> current) {
+    private Object evaluateWithStoredBoundVars(String importsSpec, List<String> current) {
         Object result
         String variableBlocks = null
         // To make groovysh behave more like an interpreter, we need to retrieve all bound
@@ -294,23 +298,21 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
         setLastResult(result = interp.evaluate(buff))
 
         if (variableBlocks) {
-            Map<String, Object> boundVarValues = interp.context.getVariable(COLLECTED_BOUND_VARS_MAP_VARNAME)
+            def boundVarValues = (Map<String, Object>) interp.context.getVariable(COLLECTED_BOUND_VARS_MAP_VARNAME)
             boundVarValues.each({ String name, Object value -> interp.context.setVariable(name, value) })
         }
 
         return result
     }
 
-
-
-    protected Object executeCommand(final String line) {
+    protected Object executeCommand(String line) {
         return super.execute(line)
     }
 
     /**
      * Display the given buffer.
      */
-    void displayBuffer(final List buffer) {
+    void displayBuffer(List buffer) {
         assert buffer
 
         buffer.eachWithIndex { line, index ->
@@ -328,16 +330,15 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     // Prompt
     //
 
-    private final AnsiRenderer prompt = new AnsiRenderer()
-
-    /*
-        Builds the command prompt name in 1 of 3 ways:
-           1.  Checks the groovysh.prompt property passed into groovysh script.   -Dgroovysh.prompt="hello"
-           2.  Checks an environment variable called GROOVYSH_PROMPT.             export GROOVYSH_PROMPT
-           3.  If no value is defined returns the default groovy shell prompt.
-
-        The code will always assume you want the line number in the prompt.  To implement differently overhead the render
-        prompt variable.
+    /**
+     * Builds the command prompt name in 1 of 3 ways:
+     * <ol>
+     * <li>Checks the groovysh.prompt property passed into groovysh script: {@code -Dgroovysh.prompt="hello"}
+     * <li>Checks an environment variable called GROOVYSH_PROMPT: {@code export GROOVYSH_PROMPT}
+     * <li>If no value is defined returns the default groovy shell prompt.
+     * </ol>
+     * The code will always assume you want the line number in the prompt. To
+     * implement differently overhead the render prompt variable.
      */
     private String buildPrompt() {
         def lineNum = formatLineNumber(buffers.current().size())
@@ -377,13 +378,13 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     }
 
     String renderPrompt() {
-        return prompt.render( buildPrompt() )
+        return AnsiRenderer.render(buildPrompt())
     }
 
     /**
      * Format the given number suitable for rendering as a line number column.
      */
-    protected String formatLineNumber(final int num) {
+    protected String formatLineNumber(int num) {
         assert num >= 0
 
         // Make a %03d-like string for the line number
@@ -401,29 +402,25 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     }
 
     /**
-     * Loads file from within user groovy state directory
-     * @param filename
+     * Loads file from within user groovy state directory.
      */
-    protected void loadUserScript(final String filename) {
+    @CompileDynamic
+    protected void loadUserScript(String filename) {
         assert filename
 
-        File file = new File(getUserStateDirectory(), filename)
-
+        def file = new File(getUserStateDirectory(), filename)
         if (file.exists()) {
-            Command command = registry[LoadCommand.COMMAND_NAME] as Command
-
+            def command = registry[LoadCommand.COMMAND_NAME] as Command
             if (command) {
                 log.debug("Loading user-script: $file")
 
                 // Disable the result hook for profile scripts
                 def previousHook = resultHook
                 resultHook = { result -> /* nothing */}
-
                 try {
                     command.load(file.toURI().toURL())
                 }
                 finally {
-                    // Restore the result hook
                     resultHook = previousHook
                 }
             } else {
@@ -436,30 +433,30 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     // Recording
     //
 
-    protected void maybeRecordInput(final String line) {
-        RecordCommand record = registry[RecordCommand.COMMAND_NAME]
-
+    protected void maybeRecordInput(String line) {
+        def record = (RecordCommand) registry[RecordCommand.COMMAND_NAME]
         if (record != null) {
             record.recordInput(line)
         }
     }
 
-    protected void maybeRecordResult(final Object result) {
-        RecordCommand record = registry[RecordCommand.COMMAND_NAME]
-
+    protected void maybeRecordResult(Object result) {
+        def record = (RecordCommand) registry[RecordCommand.COMMAND_NAME]
         if (record != null) {
             record.recordResult(result)
         }
     }
 
     protected void maybeRecordError(Throwable cause) {
-        RecordCommand record = registry[RecordCommand.COMMAND_NAME]
-
+        def record = (RecordCommand) registry[RecordCommand.COMMAND_NAME]
         if (record != null) {
+            Throwable error
             if (getPreference(SANITIZE_PREFERENCE_KEY, 'false')) {
-                cause = StackTraceUtils.deepSanitize(cause)
+                error = StackTraceUtils.deepSanitize(cause)
+            } else {
+                error = cause
             }
-            record.recordError(cause)
+            record.recordError(error)
         }
     }
 
@@ -467,7 +464,7 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     // Hooks
     //
 
-    final Closure defaultResultHook = {Object result ->
+    final Closure defaultResultHook = { Object result ->
         boolean showLastResult = !io.quiet && (io.verbose || getPreference(SHOW_LAST_RESULT_PREFERENCE_KEY, 'false'))
         if (showLastResult) {
             // avoid String.valueOf here because it bypasses pretty-printing of Collections,
@@ -478,7 +475,7 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
 
     Closure resultHook = defaultResultHook
 
-    private void setLastResult(final Object result) {
+    private void setLastResult(Object result) {
         if (resultHook == null) {
             throw new IllegalStateException('Result hook is not set')
         }
@@ -502,7 +499,7 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
             Writer data = new org.apache.groovy.io.StringBuilderWriter()
             PrintWriter writer = new PrintWriter(data)
             ErrorCollector collector = ((MultipleCompilationErrorsException) cause).getErrorCollector()
-            Iterator<Message> msgIterator = collector.getErrors().iterator()
+            Iterator<? extends Message> msgIterator = collector.getErrors().iterator()
             while (msgIterator.hasNext()) {
                 Message errorMsg = msgIterator.next()
                 errorMsg.write(writer)
@@ -514,7 +511,6 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
         } else {
             io.err.println("@|bold,red ${cause.message}|@")
 
-
             maybeRecordError(cause)
 
             if (log.debug) {
@@ -523,13 +519,8 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
             }
             else {
                 boolean sanitize = getPreference(SANITIZE_PREFERENCE_KEY, 'false')
-
                 // Sanitize the stack trace unless we are in verbose mode, or the user has request otherwise
-                if (!io.verbose && sanitize) {
-                    cause = StackTraceUtils.deepSanitize(cause)
-                }
-
-                def trace = cause.stackTrace
+                def trace = (sanitize && !io.verbose ? StackTraceUtils.deepSanitize(cause) : cause).stackTrace
 
                 def buff = new StringBuilder()
 
@@ -565,13 +556,13 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     }
 
     // protected for mocking in tests
-    protected String getPreference(final String key, final String theDefault) {
+    protected String getPreference(String key, String theDefault) {
         return Preferences.get(key, theDefault)
     }
 
     Closure errorHook = defaultErrorHook
 
-    private void displayError(final Throwable cause) {
+    private void displayError(Throwable cause) {
         if (errorHook == null) {
             throw new IllegalStateException('Error hook is not set')
         }
@@ -586,38 +577,36 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     }
 
     /**
-    * Run Interactive Shell with optional initial script and files to load
+    * Run the Interactive Shell with optional initial script and files to load.
     */
-    int run(final String evalString, final List<String> filenames) {
+    int run(String evalString, List<String> filenames) {
         List<String> startCommands = []
-
-        if (evalString != null && evalString.trim().size() > 0) {
+        if (evalString?.trim()) {
             startCommands.add(evalString)
         }
-        if (filenames != null && filenames.size() > 0) {
-            startCommands.addAll(filenames.collect({String it -> "${LoadCommand.COMMAND_NAME} $it"}))
+        if (filenames) {
+            filenames.each {
+                startCommands.add("${LoadCommand.COMMAND_NAME} $it".toString())
+            }
         }
         return run(startCommands.join('\n'))
     }
 
     /**
-     * Run Interactive Shell with initial command
+     * Run the Interactive Shell with initial command.
      */
-    int run(final String commandLine) {
+    int run(String commandLine) {
         def code
-
         try {
             loadUserScript('groovysh.profile')
             loadUserScript('groovysh.rc')
 
             // Setup the interactive runner
-            runner = new InteractiveShellRunner(
-                    this,
-                    this.&renderPrompt as Closure)
+            runner = new InteractiveShellRunner(this, this.&renderPrompt)
 
             // if args were passed in, just execute as a command
             // (but cygwin gives an empty string, so ignore that)
-            if (commandLine != null && commandLine.trim().size() > 0) {
+            if (commandLine?.trim()) {
                 runner.wrappedInputStream.insert(commandLine + '\n')
             }
 
@@ -634,18 +623,14 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
             // And let 'er rip... :-)
             runner.run()
 
-
             code = 0
         } catch (ExitNotification n) {
-            log.debug("Exiting w/code: ${n.code}")
-
             code = n.code
-        }
-        catch (Throwable t) {
+            log.debug("Exiting w/code: $code")
+        } catch (Throwable t) {
+            code = 1
             io.err.println(messages.format('info.fatal', t))
             t.printStackTrace(io.err)
-
-            code = 1
         }
 
         assert code != null // This should never happen
@@ -653,7 +638,6 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
         return code
     }
 
-
     /**
      * maybe displays log information and a welcome message
      * @param term
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/InteractiveShellRunner.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/InteractiveShellRunner.groovy
index 6bced9633f..b177fd28e7 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/InteractiveShellRunner.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/InteractiveShellRunner.groovy
@@ -18,6 +18,8 @@
  */
 package org.apache.groovy.groovysh
 
+import groovy.transform.AutoFinal
+import groovy.transform.CompileStatic
 import jline.console.ConsoleReader
 import jline.console.completer.AggregateCompleter
 import jline.console.completer.CandidateListCompletionHandler
@@ -32,58 +34,53 @@ import org.codehaus.groovy.tools.shell.util.Preferences
 /**
  * Support for running a {@link Shell} interactively using the JLine library.
  */
+@AutoFinal @CompileStatic
 class InteractiveShellRunner extends ShellRunner implements Runnable {
-    ConsoleReader reader
 
+    ConsoleReader reader
     final Closure prompt
-
     final CommandsMultiCompleter completer
-    WrappedInputStream wrappedInputStream
+    WrappedInputStream  wrappedInputStream
 
-    InteractiveShellRunner(final Groovysh shell, final Closure prompt) {
+    InteractiveShellRunner(Groovysh shell, Closure prompt) {
         super(shell)
 
         this.prompt = prompt
         this.wrappedInputStream = new WrappedInputStream(shell.io.inputStream)
         this.reader = new ConsoleReader(wrappedInputStream, shell.io.outputStream)
 
-        CompletionHandler currentCompletionHandler = this.reader.getCompletionHandler()
+        CompletionHandler currentCompletionHandler = reader.getCompletionHandler()
         if (currentCompletionHandler instanceof CandidateListCompletionHandler) {
-            // have to downcast because methods not part of the interface
-            ((CandidateListCompletionHandler) currentCompletionHandler).setStripAnsi(true)
-            ((CandidateListCompletionHandler) currentCompletionHandler).setPrintSpaceAfterFullCompletion(false)
+            currentCompletionHandler.setStripAnsi(true)
+            currentCompletionHandler.setPrintSpaceAfterFullCompletion(false)
         }
 
-
         // expand events ia an advanced feature of JLine that clashes with Groovy syntax (e.g. invoke "2!=3")
-        this.reader.expandEvents = false
-
+        reader.expandEvents = false
 
         // complete groovysh commands, display, import, ... as first word in line
-        this.completer = new CommandsMultiCompleter()
-        reader.addCompleter(this.completer)
+        completer = new CommandsMultiCompleter()
 
         def reflectionCompleter = new org.apache.groovy.groovysh.completion.antlr4.ReflectionCompleter(shell)
 
         def classnameCompleter = new org.apache.groovy.groovysh.completion.antlr4.CustomClassSyntaxCompleter(shell)
 
-        def identifierCompleters = [
-                new org.apache.groovy.groovysh.completion.antlr4.KeywordSyntaxCompleter(),
-                new org.apache.groovy.groovysh.completion.antlr4.VariableSyntaxCompleter(shell),
-                classnameCompleter,
-                new org.apache.groovy.groovysh.completion.antlr4.ImportsSyntaxCompleter(shell),
+        List<org.apache.groovy.groovysh.completion.antlr4.IdentifierCompleter> identifierCompleters = [
+            new org.apache.groovy.groovysh.completion.antlr4.KeywordSyntaxCompleter(),
+            new org.apache.groovy.groovysh.completion.antlr4.VariableSyntaxCompleter(shell),
+            classnameCompleter,
+            new org.apache.groovy.groovysh.completion.antlr4.ImportsSyntaxCompleter(shell)
         ]
 
         def filenameCompleter = new FileNameCompleter(false)
 
-        def completerArgs = [shell, reflectionCompleter, classnameCompleter, identifierCompleters, filenameCompleter]
-
-        reader.addCompleter(new org.apache.groovy.groovysh.completion.antlr4.GroovySyntaxCompleter(*completerArgs))
+        reader.addCompleter(completer)
+        reader.addCompleter(new org.apache.groovy.groovysh.completion.antlr4.GroovySyntaxCompleter(shell, reflectionCompleter, classnameCompleter, identifierCompleters, filenameCompleter))
     }
 
     @Override
     void run() {
-        for (Command command in shell.registry.commands()) {
+        for (command in shell.registry.commands()) {
             completer.add(command)
         }
 
@@ -95,19 +92,6 @@ class InteractiveShellRunner extends ShellRunner implements Runnable {
         super.run()
     }
 
-    void setHistory(final FileHistory history) {
-        reader.history = history
-        def dir = history.file.parentFile
-
-        if (!dir.exists()) {
-            dir.mkdirs()
-
-            log.debug("Created base directory for history file: $dir")
-        }
-
-        log.debug("Using history file: $history.file")
-    }
-
     @Override
     protected String readLine() {
         try {
@@ -153,20 +137,33 @@ class InteractiveShellRunner extends ShellRunner implements Runnable {
         }
     }
 
+    void setHistory(FileHistory history) {
+        reader.history = history
+        def dir = history.file.parentFile
+
+        if (!dir.exists()) {
+            dir.mkdirs()
+
+            log.debug("Created base directory for history file: $dir")
+        }
+
+        log.debug("Using history file: $history.file")
+    }
 }
 
 /**
  * Completer for interactive shells.
  */
-class CommandsMultiCompleter
-        extends AggregateCompleter {
-    protected final Logger log = Logger.create(this.class)
+@AutoFinal @CompileStatic
+class CommandsMultiCompleter extends AggregateCompleter {
+
+    protected final Logger log = Logger.create(getClass())
 
     List/*<Completer>*/ list = []
 
     private boolean dirty = false
 
-    def add(final Command command) {
+    def add(Command command) {
         assert command
 
         //
@@ -193,7 +190,7 @@ class CommandsMultiCompleter
     }
 
     @Override
-    int complete(final String buffer, final int pos, final List cand) {
+    int complete(String buffer, int pos, List cand) {
         assert buffer != null
 
         //
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Interpreter.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Interpreter.groovy
index cc21a239fb..ac3cc4a430 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Interpreter.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Interpreter.groovy
@@ -18,63 +18,67 @@
  */
 package org.apache.groovy.groovysh
 
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.runtime.FormatHelper
+import org.codehaus.groovy.runtime.InvokerHelper
 import org.codehaus.groovy.runtime.MethodClosure
 import org.codehaus.groovy.tools.shell.util.Logger
 
-import java.lang.reflect.Method
+interface Evaluator {
+    Object evaluate(Collection<String> strings)
+}
 
 /**
  * Helper to interpret a source buffer.
  */
-class Interpreter implements Evaluator
-{
-    static final String SCRIPT_FILENAME = 'groovysh_evaluate'
+@AutoFinal @CompileStatic
+class Interpreter implements Evaluator {
 
-    private final Logger log = Logger.create(this.class)
+    protected static final String SCRIPT_FILENAME = 'groovysh_evaluate'
 
-    private final GroovyShell shell
+    private final Logger log = Logger.create(getClass())
 
-    Interpreter(final ClassLoader classLoader, final Binding binding) {
-        this(classLoader, binding, CompilerConfiguration.DEFAULT)
-    }
+    private final GroovyShell shell
 
-    Interpreter(final ClassLoader classLoader, final Binding binding, CompilerConfiguration configuration) {
+    Interpreter(ClassLoader classLoader, Binding binding, CompilerConfiguration configuration = CompilerConfiguration.DEFAULT) {
         assert classLoader
         assert binding
         shell = new GroovyShell(classLoader, binding, configuration)
     }
 
+    GroovyClassLoader getClassLoader() {
+        return shell.getClassLoader()
+    }
+
     Binding getContext() {
         // GROOVY-9584: leave as call to getter not property access to avoid potential context variable in binding
         return shell.getContext()
     }
 
-    GroovyClassLoader getClassLoader() {
-        return shell.classLoader
-    }
-
     GroovyShell getShell() {
         return shell
     }
 
-    @Override
-    def evaluate(final Collection<String> buffer) {
+    //--------------------------------------------------------------------------
+
+    @Override @CompileDynamic
+    Object evaluate(Collection<String> buffer) {
         assert buffer
 
-        def source = buffer.join(Parser.NEWLINE)
+        String source = buffer.join(Parser.NEWLINE)
 
-        def result
+        Object result = null
 
-        Class type
+        Class type = null
         try {
-            Script script = shell.parse(source, SCRIPT_FILENAME)
-            type = script.getClass()
-
+            type = shell.parseClass(new GroovyCodeSource(source, SCRIPT_FILENAME, GroovyShell.DEFAULT_CODE_BASE))
+            Script script = InvokerHelper.createScript(type, context)
             log.debug("Compiled script: $script")
 
-            if (type.declaredMethods.any {Method it -> it.name == 'main' }) {
+            if (type.getDeclaredMethods().any { it.name == 'main' }) {
                 result = script.run()
             }
 
@@ -82,28 +86,23 @@ class Interpreter implements Evaluator
             log.debug("Evaluation result: ${FormatHelper.toString(result)} (${result?.getClass()})")
 
             // Keep only the methods that have been defined in the script
-            type.declaredMethods.each { Method m ->
-                if (!(m.name in [ 'main', 'run' ] || m.name.startsWith('super$') || m.name.startsWith('class$') || m.name.startsWith('$'))) {
-                    log.debug("Saving method definition: $m.name")
-
-                    context["${m.name}"] = new MethodClosure(type.newInstance(), m.name)
+            type.getDeclaredMethods().each { m ->
+                String name = m.name
+                if (!(name == 'main' || name == 'run' || name.startsWith('super$') || name.startsWith('class$') || name.startsWith('$'))) {
+                    log.debug("Saving script method definition: $name")
+                    context[name] = new MethodClosure(script, name)
                 }
             }
-        }
-        finally {
-            // Remove the script class generated
+        } finally {
+            // Remove the generated script class
             if (type?.name) {
-                classLoader.removeClassCacheEntry(type?.name)
+                classLoader.removeClassCacheEntry(type.name)
             }
 
-            // Remove the inline closures from the cache as well
+            // Remove the inline closures as well
             classLoader.removeClassCacheEntry('$_run_closure')
         }
 
         return result
     }
 }
-
-interface Evaluator {
-    def evaluate(final Collection<String> buffer)
-}
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
index 705213e198..922dcd15aa 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/Main.groovy
@@ -20,6 +20,7 @@ package org.apache.groovy.groovysh
 
 import groovy.cli.internal.CliBuilderInternal
 import groovy.cli.internal.OptionAccessor
+import groovy.transform.AutoFinal
 import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
 import jline.TerminalFactory
@@ -49,7 +50,7 @@ import static org.apache.groovy.util.SystemUtil.setSystemPropertyFrom
  *
  * Main CLI entry-point for <tt>groovysh</tt>.
  */
-@CompileStatic
+@AutoFinal @CompileStatic
 class Main {
     final Groovysh groovysh
 
@@ -74,7 +75,7 @@ class Main {
      * @param main must have a Groovysh member that has an IO member.
      */
     @CompileDynamic
-    static void main(final String[] args) {
+    static void main(String[] args) {
         MessageSource messages = new MessageSource(Main)
         def cli = new CliBuilderInternal(usage: 'groovysh [options] [...]', stopAtNonOption: false,
                 header: messages['cli.option.header'])
@@ -153,10 +154,13 @@ class Main {
         if (options.e) {
             evalString = options.getOptionValue('e')
         }
-        def configuration = new CompilerConfiguration(System.getProperties())
-        configuration.setParameters((boolean) options.hasOption("pa"))
 
-        List<String> filenames = options.arguments()
+        start(io, evalString, options.arguments(), options.hasOption('pa'))
+    }
+
+    private static void start(IO io, String evalString, List<String> filenames, boolean parameters) {
+        def configuration = new CompilerConfiguration(System.getProperties())
+        configuration.setParameters(parameters)
         Main main = new Main(io, configuration)
         main.startGroovysh(evalString, filenames)
     }
@@ -207,6 +211,7 @@ class Main {
      * @param type: one of 'auto', 'unix', ('win', 'windows'), ('false', 'off', 'none')
      * @param suppressColor only has effect when ansi is enabled
      */
+    @AutoFinal(enabled=false)
     static void setTerminalType(String type, boolean suppressColor) {
         assert type != null
 
@@ -260,7 +265,7 @@ class Main {
     }
 
     @Deprecated
-    static void setSystemProperty(final String nameValue) {
+    static void setSystemProperty(String nameValue) {
         setSystemPropertyFrom(nameValue)
     }
 
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DefaultCommandsRegistrar.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DefaultCommandsRegistrar.groovy
index 9fc9ef430c..522fe10bdf 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DefaultCommandsRegistrar.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/util/DefaultCommandsRegistrar.groovy
@@ -40,11 +40,7 @@ import org.apache.groovy.groovysh.commands.SetCommand
 import org.apache.groovy.groovysh.commands.ShowCommand
 
 /**
- * Registers {@link Command} classes from an XML file like:
- * <commands>
- *  <command>org.apache.groovy.groovysh.commands.HelpCommand</command>
- * ...
- * </commands>
+ * Registers default {@link Command} instances.
  */
 class DefaultCommandsRegistrar {
 
@@ -52,12 +48,11 @@ class DefaultCommandsRegistrar {
 
     DefaultCommandsRegistrar(final Shell shell) {
         assert shell != null
-
         this.shell = shell
     }
 
     void register() {
-        def commands = [
+        List<Command> commands = [
             new HelpCommand(shell),
             new ExitCommand(shell),
             new ImportCommand(shell),
@@ -77,12 +72,12 @@ class DefaultCommandsRegistrar {
             new RegisterCommand(shell),
         ]
 
-        if (!System.getProperty("groovysh.disableDocCommand")?.toBoolean()) {
+        if (!Boolean.getBoolean('groovysh.disableDocCommand')) {
             commands.add(new DocCommand(shell))
         }
 
-        for (Command classname in commands) {
-            shell.register(classname)
+        for (command in commands) {
+            shell.register(command)
         }
     }
 }
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/CompleterTestSupport.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/CompleterTestSupport.groovy
index 45dc56e6f0..f173dc633c 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/CompleterTestSupport.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/CompleterTestSupport.groovy
@@ -20,48 +20,39 @@ package org.apache.groovy.groovysh
 
 import groovy.mock.interceptor.MockFor
 import groovy.test.GroovyTestCase
-import org.codehaus.groovy.tools.shell.IO
 import org.apache.groovy.groovysh.completion.antlr4.IdentifierCompleter
 import org.apache.groovy.groovysh.completion.antlr4.ReflectionCompleter
-import org.apache.groovy.groovysh.util.PackageHelper
 import org.apache.groovy.groovysh.util.PackageHelperImpl
+import org.codehaus.groovy.tools.shell.IO
 
 abstract class CompleterTestSupport extends GroovyTestCase {
 
-    BufferManager bufferManager = new BufferManager()
-    IO testio
     ByteArrayOutputStream mockOut
     ByteArrayOutputStream mockErr
-    MockFor groovyshMocker
-    MockFor packageHelperMocker
-    PackageHelper mockPackageHelper
+    IO testio
+
     MockFor reflectionCompleterMocker
+    MockFor packageHelperMocker
     MockFor idCompleterMocker
+    MockFor groovyshMocker
 
     @Override
-    void setUp() {
+    protected void setUp() {
         super.setUp()
         mockOut = new ByteArrayOutputStream()
         mockErr = new ByteArrayOutputStream()
         testio = new IO(new ByteArrayInputStream(), mockOut, mockErr)
+
         reflectionCompleterMocker = new MockFor(ReflectionCompleter)
+        packageHelperMocker = new MockFor(PackageHelperImpl)
         idCompleterMocker = new MockFor(IdentifierCompleter)
 
         groovyshMocker = new MockFor(Groovysh)
-        groovyshMocker.demand.getClass(0..1) { Groovysh }
-        groovyshMocker.demand.createDefaultRegistrar { { shell -> null } }
-        groovyshMocker.demand.getIo(0..2) { testio }
-        packageHelperMocker = new MockFor(PackageHelperImpl)
-        def registry = new CommandRegistry()
-        groovyshMocker.demand.getRegistry(0..1) { registry }
-        packageHelperMocker.demand.getContents(6) { ['java', 'test'] }
-        groovyshMocker.demand.getIo(0..2) { testio }
-        for (i in 1..19) {
-            groovyshMocker.demand.getIo(0..1) { testio }
-            groovyshMocker.demand.add(0..1) {}
-            groovyshMocker.demand.getIo(0..1) { testio }
-        }
-        groovyshMocker.demand.getRegistry(0..1) { registry }
-        groovyshMocker.demand.getBuffers(0..2) { bufferManager }
+        // no-arg constructor
+        groovyshMocker.demand.getClass( 1) { Groovysh }
+        groovyshMocker.demand.getIo(0..21) { testio }
+        groovyshMocker.demand.register(18) { it }
+        // new command
+        groovyshMocker.demand.getIo( 0..3) { testio }
     }
 }
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ImportCompleterTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ImportCompleterTest.groovy
index f052999f2f..de1bc1d9c2 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ImportCompleterTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ImportCompleterTest.groovy
@@ -20,8 +20,11 @@ package org.apache.groovy.groovysh
 
 import jline.console.completer.Completer
 import org.apache.groovy.groovysh.commands.ImportCommand
+import org.apache.groovy.groovysh.util.PackageHelper
 
-class ImportCompleterTest extends CompleterTestSupport {
+final class ImportCompleterTest extends CompleterTestSupport {
+
+    private PackageHelper mockPackageHelper
 
     void testEmpty() {
         mockPackageHelper = new MockPackageHelper(['java', 'test'])
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTestSupport.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTestSupport.groovy
index d1565b1f59..e11ba4b4a9 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTestSupport.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTestSupport.groovy
@@ -25,41 +25,37 @@ import jline.console.ConsoleReader
 import org.codehaus.groovy.tools.shell.IO
 
 /**
- * Support for testing {@link Command} instances.
+ * Support for testing {@link ShellRunner} instances.
  */
 abstract class ShellRunnerTestSupport extends GroovyTestCase {
 
-    protected IO testio
     protected BufferedOutputStream mockOut
     protected BufferedOutputStream mockErr
+    protected IO testio
 
     protected MockFor shellMocker
     protected StubFor readerStubber
 
     @Override
-    void setUp() {
+    protected void setUp() {
         super.setUp()
+
         mockOut = new BufferedOutputStream(new ByteArrayOutputStream())
         mockErr = new BufferedOutputStream(new ByteArrayOutputStream())
         testio = new IO(new ByteArrayInputStream(), mockOut, mockErr)
         testio.verbosity = IO.Verbosity.QUIET
-        // setup mock and stub with calls expected from InteractiveShellRunner Constructor
 
         shellMocker = new MockFor(Groovysh)
-        // when run with compileStatic
-        shellMocker.demand.getClass(0..1) {Groovysh}
-        shellMocker.demand.createDefaultRegistrar(1) { {Shell shell -> null} }
+        // Groovysh constructor
+        shellMocker.demand.getClass(1) { Groovysh }
+        shellMocker.demand.getIo(0..21) { testio }
+        shellMocker.demand.register(18) { it }
+        // InteractiveShellRunner constructor
         shellMocker.demand.getIo(2) { testio }
-        shellMocker.demand.getRegistry(1) {new Object() {def commands() {[]} }}
-        shellMocker.demand.getHistory(1) {new Serializable(){def size() {0}; def getMaxSize() {1}}}
-        shellMocker.demand.setHistoryFull(1) {}
-        shellMocker.demand.getHistoryFull(1) {false}
-        // adding number of commands from xml file
-        for (i in 1..19) {
-            shellMocker.demand.getIo(0..1) { testio }
-            shellMocker.demand.add(0..1) { testio }
-            shellMocker.demand.getIo(0..1) { testio }
-        }
+        shellMocker.demand.getRegistry(1) { new Object() { def commands() {[]} } }
+        shellMocker.demand.getHistory(1) { new Serializable() { def size() {0}; def getMaxSize() {1} } }
+        shellMocker.demand.setHistoryFull(1) { }
+        shellMocker.demand.getHistoryFull(1) { false }
 
         readerStubber = new StubFor(ConsoleReader)
         readerStubber.demand.setExpandEvents {}
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/completion/GroovySyntaxCompleterTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/completion/GroovySyntaxCompleterTest.groovy
index af1a6f9fd6..591f2d93e5 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/completion/GroovySyntaxCompleterTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/completion/GroovySyntaxCompleterTest.groovy
@@ -19,208 +19,159 @@
 package org.apache.groovy.groovysh.completion
 
 import groovy.mock.interceptor.MockFor
+import org.apache.groovy.groovysh.BufferManager
 import org.apache.groovy.groovysh.CommandRegistry
 import org.apache.groovy.groovysh.CompleterTestSupport
 import org.apache.groovy.groovysh.Groovysh
-import org.apache.groovy.groovysh.commands.ImportCommand
 import org.apache.groovy.groovysh.completion.antlr4.GroovySyntaxCompleter
 import org.apache.groovy.groovysh.completion.antlr4.IdentifierCompleter
 import org.apache.groovy.groovysh.completion.antlr4.ReflectionCompleter
 
-class GroovySyntaxCompleterTest extends CompleterTestSupport {
+final class GroovySyntaxCompleterTest extends CompleterTestSupport {
 
-    void testEmpty() {
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
+    private final BufferManager bufferManager = new BufferManager()
+    private final List<CharSequence> candidates = []
+
+    private int runTest(String buffer, int cursor = buffer.length(), FileNameCompleter fileNameCompleter = null) {
+        if (buffer) {
+            def registry = new CommandRegistry()
+            groovyshMocker.demand.getRegistry(1) { registry }
+            groovyshMocker.demand.getBuffers(0..1) { bufferManager }
+        }
+
+        int result = Integer.MIN_VALUE
         groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            assert -1 == completer.complete('', 0, [])
+            Groovysh groovysh = new Groovysh()
+            IdentifierCompleter identifierCompleter = idCompleterMocker.proxyDelegateInstance()
+            ReflectionCompleter reflectionCompleter = reflectionCompleterMocker.proxyInstance(groovysh)
+            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovysh, reflectionCompleter, identifierCompleter, [identifierCompleter], fileNameCompleter)
+
+            result = completer.complete(buffer, cursor, candidates)
         }
+        return result
+    }
+
+    void testEmpty() {
+        int result = runTest('')
+
+        assert result == -1
+        assert candidates.isEmpty()
     }
 
     void testIdentifier() {
         idCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['jav']); candidates << 'javup'; candidates << 'java.lang.String' ; true}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'jav'
-            // in the shell, only Classes in the default package occur,but well...
-            assert 0 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['javup', 'java.lang.String'] == candidates
-        }
+
+        int result = runTest('jav')
+
+        assert result == 0
+        assert candidates == ['javup', 'java.lang.String']
     }
 
     void testIdentifierAfterLCurly() {
         idCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['{', 'jav']); candidates << 'javup'; candidates << 'java.lang.String' ; true}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = '{jav'
-            // in the shell, only Classes in the default package occur,but well...
-            assert 1 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['javup', 'java.lang.String'] == candidates
-        }
+
+        int result = runTest('{jav')
+
+        assert result == 1
+        assert candidates == ['javup', 'java.lang.String']
     }
 
     void testMember() {
         reflectionCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Math', '.', 'ma']); candidates << 'max('; 5}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'Math.ma'
-            assert 5 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['max('] == candidates
-        }
+
+        int result = runTest('Math.ma')
+
+        assert result == 5
+        assert candidates == ['max(']
     }
 
     void testMemberOptionalDot() {
         reflectionCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Math', '?.', 'ma']); candidates << 'max('; 6}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'Math?.ma'
-            assert 6 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['max('] == candidates
-        }
+
+        int result = runTest('Math?.ma')
+
+        assert result == 6
+        assert candidates == ['max(']
     }
 
     void testMemberSpreadDot() {
         reflectionCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['[', '\'foo\'', ']', '*.', 'len']); candidates << 'length()'; 9}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = '[\'foo\']*.len'
-            assert 9 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['length()'] == candidates
-        }
+
+        int result = runTest('[\'foo\']*.len')
+
+        assert result == 9
+        assert candidates == ['length()']
     }
 
     void testMemberAfterMethod() {
         reflectionCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Fo', '.', 'ba', '(', ')', '.', 'xyz']); candidates << 'xyzabc'; 0}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'Fo.ba().xyz'
-            // xyz cannot be not a var here
-            assert 0 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['xyzabc'] == candidates
-        }
+
+        // xyz cannot be not a var here
+        int result = runTest('Fo.ba().xyz')
+
+        assert result == 0
+        assert candidates == ['xyzabc']
     }
 
     void testIdentfierAfterDotAfterParens() {
         idCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Foo', '.', 'bar', '(', 'xyz']); candidates << 'xyzabc'; true}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'Foo.bar(xyz'
-            assert 8 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['xyzabc'] == candidates
-        }
+
+        int result = runTest('Foo.bar(xyz')
+
+        assert result == 8
+        assert candidates == ['xyzabc']
     }
 
     void testIndentifierAfterParensBeforeDot() {
         idCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Foo', '.', 'bar', '(', 'xyz']); candidates << 'xyzabc'; true}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            // cursor is BEFORE dot
-            assert 8 == completer.complete('Foo.bar(xyz.', 'Foo.bar(xyz'.length(), candidates)
-            assert ['xyzabc'] == candidates
-        }
+
+        int result = runTest('Foo.bar(xyz.', 'Foo.bar(xyz'.length()) // cursor is BEFORE dot
+
+        assert result == 8
+        assert candidates == ['xyzabc']
     }
 
     void testDoubleIdentifier() {
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'String jav'
-            assert -1 == completer.complete(buffer, buffer.length(), candidates)
-            assert [] == candidates
-        }
+        int result = runTest('String jav')
+
+        assert result == -1
+        assert candidates == []
     }
 
     void testInfixKeyword() {
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'class Foo ext'
-            assert 10 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['extends'] == candidates
-        }
+        int result = runTest('class Foo ext')
+
+        assert result == 10
+        assert candidates == ['extends']
     }
 
     void testInstanceof() {
         idCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['x', 'instanceof', 'P']); candidates << 'Property'; 13}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
 
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'x instanceof P'
-            assert 13 == completer.complete(buffer, buffer.length(), candidates)
-            assert 'Property' in candidates
-        }
-    }
+        int result = runTest('x instanceof P')
 
+        assert result == 13
+        assert candidates.contains('Property')
+    }
 
     void testAfterSemi() {
         // evaluation of all is dangerous, but the reflectionCompleter has to deal with this
         reflectionCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['deletehardDisk', '(', ')', ';', 'foo', '.', 'subs']); candidates << 'substring('; 22}
 
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        // mock doing the right thing
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'deletehardDisk(); foo.subs'
-            assert 22 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['substring('] == candidates
-        }
+        int result = runTest('deletehardDisk(); foo.subs')
+
+        assert result == 22
+        assert candidates == ['substring(']
     }
 
     void testAfterOperator() {
@@ -228,108 +179,74 @@ class GroovySyntaxCompleterTest extends CompleterTestSupport {
         reflectionCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['a', '=', 'foo', '.', 'subs']); candidates << 'substring('; 9}
 
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        // mock doing the right thing
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'a = foo.subs'
-            assert 9 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['substring('] == candidates
-        }
+        int result = runTest('a = foo.subs')
+
+        assert result == 9
+        assert candidates == ['substring(']
     }
 
     void testDontEvaluateAfterCommand() {
-        CommandRegistry registry = new CommandRegistry()
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        // mock asserting nothing gets evaluated
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            // import command prevents reflection completion
-            registry.register(new ImportCommand(groovyshMock))
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'import foo'
-            assert -1 == completer.complete(buffer, buffer.length(), candidates)
-            assert [] == candidates
-        }
+        // import command prevents reflection completion
+        int result = runTest('import foo')
+
+        assert result == -1
+        assert candidates == []
     }
 
     void _disabled_testAfterGString() { // should we prohibit this?
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        // mock asserting GString is not evaluated
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = '"\${foo.delete()}".subs'
-            assert candidates == [] && -1 == completer.complete(buffer, buffer.length(), candidates)
-        }
+        int result = runTest('"\${foo.delete()}".subs') // GString not evaluated
+
+        assert result == -1
+        assert candidates == []
     }
 
     void _fixme_testInStringFilename() {
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        MockFor filenameCompleterMocker = new MockFor(FileNameCompleter)
-        String linestart = /foo('/ // ends with single hyphen
+        def filenameCompleterMocker = new MockFor(FileNameCompleter)
+        String linestart = "foo('" // ends with apostrophe
         String pathstart = '/usr/foobar'
-        String buffer = linestart + pathstart
+
         filenameCompleterMocker.demand.complete(2) { bufferline, cursor, candidates ->
-            assert(bufferline == pathstart)
-            assert(cursor == pathstart.length())
+            assert bufferline == pathstart
+            assert cursor == pathstart.length()
             candidates << 'foobar'
             5
         }
-        groovyshMocker.use { filenameCompleterMocker.use {
-            FileNameCompleter mockFileComp = new FileNameCompleter()
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], mockFileComp)
-            def candidates = []
-            assert 'foo(\'/usr/'.length() == completer.complete(buffer, buffer.length(), candidates)
-            assert ['foobar'] == candidates
-        }}
+
+        String buffer = linestart + pathstart
+        int result = runTest(buffer, buffer.length(), filenameCompleterMocker.proxyDelegateInstance())
+
+        assert result == "foo('/usr/".length()
+        assert candidates == ['foobar']
     }
 
     void _fixme_testInStringFilenameBlanks() {
-        // test with blanks (non-tokens) before the first hyphen
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        MockFor filenameCompleterMocker = new MockFor(FileNameCompleter)
-        String linestart = 'x = \'' // ends with single hyphen
+        // test with blanks (non-tokens) before the apostrophe
+        def filenameCompleterMocker = new MockFor(FileNameCompleter)
+        String linestart = 'x = \'' // ends with apostrophe
         String pathstart = '/usr/foobar'
-        String buffer = linestart + pathstart
+
         filenameCompleterMocker.demand.complete(1) {bufferline, cursor, candidates ->
             assert bufferline == pathstart
             assert cursor == pathstart.length()
-            candidates << 'foobar'; 5}
-        groovyshMocker.use { filenameCompleterMocker.use {
-            FileNameCompleter mockFileComp = new FileNameCompleter()
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], mockFileComp)
-            def candidates = []
-            assert 'x = \'/usr/'.length() == completer.complete(buffer, buffer.length(), candidates)
-            assert ['foobar'] == candidates
-        }}
+            candidates << 'foobar'
+            5
+        }
+
+        String buffer = linestart + pathstart
+        int result = runTest(buffer, buffer.length(), filenameCompleterMocker.proxyDelegateInstance())
+
+        assert result == "x = '/usr/".length()
+        assert candidates == ['foobar']
     }
 
     void testInGString() {
         idCompleterMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['"$', '{', 'foo']); candidates << 'foobar'; true}
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        // mock asserting GString is not evaluated
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = '"\${foo'
-            assert 3 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['foobar'] == candidates
-        }
+
+        int result = runTest('"\${foo')
+
+        assert result == 3
+        assert candidates == ['foobar']
     }
 
     void testMultilineComplete() {
@@ -337,16 +254,10 @@ class GroovySyntaxCompleterTest extends CompleterTestSupport {
             assert(tokens*.text == ['"""xyz\nabc"""', '.', 'subs']); candidates << 'substring('; 7}
         bufferManager.buffers.add(['"""xyz'])
         bufferManager.setSelected(1)
-        IdentifierCompleter mockIdCompleter = idCompleterMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompleter mockReflComp = reflectionCompleterMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompleter completer = new GroovySyntaxCompleter(groovyshMock, mockReflComp, mockIdCompleter, [mockIdCompleter], null)
-            def candidates = []
-            String buffer = 'abc""".subs'
-            assert 7 == completer.complete(buffer, buffer.length(), candidates)
-            assert ['substring('] == candidates
-        }
+
+        int result = runTest('abc""".subs')
+
+        assert result == 7
+        assert candidates == ['substring(']
     }
 }
-