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/12/26 16:55:04 UTC

[groovy] 02/02: GROOVY-10105: streamline `GroovyStarter` -> `Groovysh` -> user's script

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

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

commit b8ba3e0e71a2994b741d1bf9478b479be1b4b91e
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Dec 26 10:37:57 2022 -0600

    GROOVY-10105: streamline `GroovyStarter` -> `Groovysh` -> user's script
---
 .../org/codehaus/groovy/tools/GroovyStarter.java   |  48 ++-
 .../codehaus/groovy/tools/LoaderConfiguration.java |   3 +-
 .../java/org/codehaus/groovy/tools/RootLoader.java |  94 +++---
 .../org/apache/groovy/groovysh/Groovysh.groovy     | 206 ++++++------
 .../groovy/groovysh/InteractiveShellRunner.groovy  |  86 ++---
 .../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 +--
 .../apache/groovy/groovysh/ErrorDisplayTest.groovy |  48 ++-
 .../groovy/groovysh/ImportCompleterTest.groovy     |   5 +-
 .../apache/groovy/groovysh/ShellRunnerTest.groovy  |  94 +++---
 .../groovy/groovysh/ShellRunnerTestSupport.groovy  |  34 +-
 .../completion/GroovySyntaxCompleterTest.groovy    | 351 ++++++++-------------
 14 files changed, 486 insertions(+), 625 deletions(-)

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/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 4ba5004f58..d5bea2bcf3 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,9 @@
  */
 package org.apache.groovy.groovysh
 
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
 import jline.console.ConsoleReader
 import jline.console.completer.AggregateCompleter
 import jline.console.completer.CandidateListCompletionHandler
@@ -33,58 +36,55 @@ 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) {
+    @CompileDynamic
+    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)
         }
 
@@ -96,20 +96,7 @@ 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
+    @Override @CompileDynamic
     protected String readLine() {
         try {
             if (Boolean.valueOf(Preferences.get(Groovysh.AUTOINDENT_PREFERENCE_KEY))) {
@@ -144,8 +131,8 @@ class InteractiveShellRunner extends ShellRunner implements Runnable {
         // we save the evicted line in case someone wants to use it with history recall
         if (shell instanceof Groovysh) {
             def history = shell.history
-            shell.historyFull = (history != null) && (history.size() >= history.maxSize)
-            if (shell.historyFull) {
+            shell.historyFull = history != null && history.size() >= history.maxSize
+            if (shell.isHistoryFull()) {
                 def first = history.first()
                 if (first) {
                     shell.evictedLine = first.value()
@@ -154,20 +141,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
 
-    def add(final Command command) {
+    def add(Command command) {
         assert command
 
         //
@@ -194,7 +194,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/ErrorDisplayTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ErrorDisplayTest.groovy
index 25717f8b32..9bb8ce7d19 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ErrorDisplayTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ErrorDisplayTest.groovy
@@ -18,55 +18,43 @@
  */
 package org.apache.groovy.groovysh
 
-import jline.console.ConsoleReader
 import jline.console.completer.CandidateListCompletionHandler
 
 class ErrorDisplayTest extends ShellRunnerTestSupport {
 
     void testInput() {
-        readerStubber.demand.readLine { 'foo' }
+        readerStubber.demand.readLine {'foo'}
         readerStubber.demand.getCompletionHandler {new CandidateListCompletionHandler()}
-        shellMocker.use {
-            readerStubber.use {
-                Groovysh shellMock = new Groovysh()
-                ConsoleReader readerStub = new ConsoleReader()
-
-                InteractiveShellRunner shellRunner = new InteractiveShellRunner(shellMock, { '>' })
-                shellRunner.reader = readerStub
-                // assert no exception
+        readerStubber.use {
+            shellMocker.use {
+                def shellRunner = new InteractiveShellRunner(new Groovysh(), {'>'})
                 shellRunner.run()
+                // no exception
             }
         }
     }
 
     void testError() {
-        readerStubber.demand.readLine { throw new StringIndexOutOfBoundsException() }
+        readerStubber.demand.readLine {throw new StringIndexOutOfBoundsException()}
         readerStubber.demand.getCompletionHandler {new CandidateListCompletionHandler()}
-        shellMocker.use {
-            readerStubber.use {
-                Groovysh shellMock = new Groovysh()
-                ConsoleReader readerStub = new ConsoleReader()
-
-                InteractiveShellRunner shellRunner = new InteractiveShellRunner(shellMock, { '>' })
-                shellRunner.reader = readerStub
-                // assert no exception
+        readerStubber.use {
+            shellMocker.use {
+                def shellRunner = new InteractiveShellRunner(new Groovysh(), {'>'})
                 shellRunner.run()
+                // no exception
             }
         }
     }
 
     void testError2() {
-        readerStubber.demand.readLine { throw new Throwable('MockException') }
+        readerStubber.demand.readLine {throw new Throwable('MockException')}
         readerStubber.demand.getCompletionHandler {new CandidateListCompletionHandler()}
-        shellMocker.use { readerStubber.use {
-            Groovysh shellMock = new Groovysh()
-            ConsoleReader readerStub = new ConsoleReader()
-
-            InteractiveShellRunner shellRunner = new InteractiveShellRunner(shellMock, {'>'})
-            shellRunner.reader = readerStub
-            // assert no exception
-            shellRunner.run()
-        }}
+        readerStubber.use {
+            shellMocker.use {
+                def shellRunner = new InteractiveShellRunner(new Groovysh(), {'>'})
+                shellRunner.run()
+                // no exception
+            }
+        }
     }
-
 }
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/ShellRunnerTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTest.groovy
index 6ec455a8df..a54e9e9889 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/apache/groovy/groovysh/ShellRunnerTest.groovy
@@ -28,43 +28,49 @@ import org.codehaus.groovy.tools.shell.util.Preferences
 class ShellRunnerTest extends GroovyTestCase {
 
     private Groovysh groovysh
+    private MockFor  readerMocker
 
     @Override
-    void setUp() {
+    protected void setUp() {
         super.setUp()
         ByteArrayOutputStream mockOut = new ByteArrayOutputStream()
         ByteArrayOutputStream mockErr = new ByteArrayOutputStream()
+        IO testio = new IO(new ByteArrayInputStream(), mockOut, mockErr)
 
-        IO testio = new IO(new ByteArrayInputStream(),
-                mockOut,
-                mockErr)
         groovysh = new Groovysh(testio)
+
+        readerMocker = new MockFor(ConsoleReader)
+        readerMocker.demand.getCompletionHandler(1) {new CandidateListCompletionHandler(stripAnsi: true)}
+        readerMocker.demand.setExpandEvents {}
+        readerMocker.demand.addCompleter(2) {}
     }
 
+    //--------------------------------------------------------------------------
+
     void testReadLineIndentPreferenceOff() {
         groovysh.buffers.buffers.add(['Foo {'])
         groovysh.buffers.select(1)
 
-        MockFor readerMocker = primedMockForConsoleReader()
-        readerMocker.demand.readLine(1) {'Foo {'}
-        MockFor preferencesMocker = new MockFor(Preferences)
+        def preferencesMocker = new MockFor(Preferences)
         preferencesMocker.demand.get(1) {'false'}
-        preferencesMocker.use {readerMocker.use {
-            InteractiveShellRunner runner = new InteractiveShellRunner(groovysh, {'>'})
+        readerMocker.demand.readLine(1) {'Foo {'}
+        preferencesMocker.use { readerMocker.use {
+            def runner = new InteractiveShellRunner(groovysh, {'>'})
             runner.readLine()
-            assertEquals(0, runner.wrappedInputStream.inserted.available())
+
+            assert runner.wrappedInputStream.inserted.available() == 0
         }}
     }
 
     void testReadLineIndentNone() {
-        MockFor readerMocker = primedMockForConsoleReader()
-        readerMocker.demand.readLine(1) {'Foo {'}
-        MockFor preferencesMocker = new MockFor(Preferences)
+        def preferencesMocker = new MockFor(Preferences)
         preferencesMocker.demand.get(1) {'true'}
-        preferencesMocker.use {readerMocker.use {
-            InteractiveShellRunner runner = new InteractiveShellRunner(groovysh, {'>'})
+        readerMocker.demand.readLine(1) {'Foo {'}
+        preferencesMocker.use { readerMocker.use {
+            def runner = new InteractiveShellRunner(groovysh, {'>'})
             runner.readLine()
-            assertEquals(0, runner.wrappedInputStream.inserted.available())
+
+            assert runner.wrappedInputStream.inserted.available() == 0
         }}
     }
 
@@ -72,66 +78,54 @@ class ShellRunnerTest extends GroovyTestCase {
         groovysh.buffers.buffers.add(['Foo {'])
         groovysh.buffers.select(1)
 
-        MockFor readerMocker = primedMockForConsoleReader()
-        readerMocker.demand.readLine(1) {'Foo {'}
-        MockFor preferencesMocker = new MockFor(Preferences)
+        def preferencesMocker = new MockFor(Preferences)
         preferencesMocker.demand.get(1) {'true'}
-        preferencesMocker.use {readerMocker.use {
-            InteractiveShellRunner runner = new InteractiveShellRunner(groovysh, {'>'})
+        readerMocker.demand.readLine(1) {'Foo {'}
+        preferencesMocker.use { readerMocker.use {
+            def runner = new InteractiveShellRunner(groovysh, {'>'})
             runner.readLine()
-            assertEquals(groovysh.indentSize, runner.wrappedInputStream.inserted.available())
+
+            assert runner.wrappedInputStream.inserted.available() == groovysh.indentSize
         }}
     }
 
     void testReadLineIndentTwo() {
         groovysh.buffers.buffers.add(['Foo { {'])
         groovysh.buffers.select(1)
-        MockFor readerMocker = primedMockForConsoleReader()
-        readerMocker.demand.readLine(1) {'Foo { {'}
-        MockFor preferencesMocker = new MockFor(Preferences)
+
+        def preferencesMocker = new MockFor(Preferences)
         preferencesMocker.demand.get(1) {'true'}
-        preferencesMocker.use {readerMocker.use {
-            InteractiveShellRunner runner = new InteractiveShellRunner(groovysh, {'>'})
+        readerMocker.demand.readLine(1) {'Foo { {'}
+        preferencesMocker.use { readerMocker.use {
+            def runner = new InteractiveShellRunner(groovysh, {'>'})
             runner.readLine()
-            assertEquals(groovysh.indentSize * 2, runner.wrappedInputStream.inserted.available())
-        }}
-    }
 
-    private MockFor primedMockForConsoleReader() {
-        def readerMocker = new MockFor(ConsoleReader)
-        CandidateListCompletionHandler clch = new CandidateListCompletionHandler()
-        clch.stripAnsi = true
-        readerMocker.demand.getCompletionHandler(1) {clch}
-        readerMocker.demand.setExpandEvents {}
-        readerMocker.demand.addCompleter(2) {}
-        readerMocker
+            assert runner.wrappedInputStream.inserted.available() == groovysh.indentSize * 2
+        }}
     }
 }
 
 class ShellRunnerTest2 extends GroovyTestCase {
 
     void testReadLinePaste() {
-        ByteArrayOutputStream mockOut = new ByteArrayOutputStream()
-        ByteArrayOutputStream mockErr = new ByteArrayOutputStream()
-
-        IO testio = new IO(new ByteArrayInputStream('Some Clipboard Content'.bytes),
-                mockOut,
-                mockErr)
-        Groovysh groovysh = new Groovysh(testio)
+        Groovysh groovysh = new Groovysh(new IO(new ByteArrayInputStream('Some Clipboard Content'.bytes),
+                                                new ByteArrayOutputStream(), new ByteArrayOutputStream()))
         groovysh.buffers.buffers.add(['Foo { {'])
         groovysh.buffers.select(1)
 
+        MockFor preferencesMocker = new MockFor(Preferences)
+        preferencesMocker.demand.get(1) {'true'}
         MockFor readerMocker = new MockFor(ConsoleReader)
         readerMocker.demand.getCompletionHandler {new CandidateListCompletionHandler()}
         readerMocker.demand.setExpandEvents {}
         readerMocker.demand.addCompleter(2) {}
         readerMocker.demand.readLine(1) {'Foo { {'}
-        MockFor preferencesMocker = new MockFor(Preferences)
-        preferencesMocker.demand.get(1) {'true'}
-        preferencesMocker.use {readerMocker.use {
-            InteractiveShellRunner runner = new InteractiveShellRunner(groovysh, {'>'})
+
+        preferencesMocker.use { readerMocker.use {
+            def runner = new InteractiveShellRunner(groovysh, {'>'})
             runner.readLine()
-            assertEquals(0, runner.wrappedInputStream.inserted.available())
+
+            assert runner.wrappedInputStream.inserted.available() == 0
         }}
     }
 }
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..9759a6cfd5 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,39 @@ 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 }
-        }
+        // InteractiveShellRunner run
+        shellMocker.demand.getRegistry(0..1) { new CommandRegistry() }
+        // InteractiveShellRunner adjustHistory
+        shellMocker.demand.getHistory(0..1) { null }
+        shellMocker.demand.setHistoryFull(0..1) { }
+        shellMocker.demand.isHistoryFull(0..1) { }
 
         readerStubber = new StubFor(ConsoleReader)
         readerStubber.demand.setExpandEvents {}
@@ -68,7 +66,5 @@ abstract class ShellRunnerTestSupport extends GroovyTestCase {
         readerStubber.demand.addCompleter {}
         readerStubber.demand.printNewline {}
         readerStubber.demand.addCompleter {}
-        shellMocker.demand.getIo(0..1) { testio }
     }
-
 }
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(']
     }
 }
-