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 22:49:37 UTC

[groovy] branch GROOVY_2_5_X updated: 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_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


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

commit 3a46f5f24bf93953f16cae74d10b07547e393ea4
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Dec 26 15:55:46 2022 -0600

    GROOVY-10105: streamline `GroovyStarter` -> `Groovysh` -> user's script
    
    2_5_X backport
---
 .../org/codehaus/groovy/tools/GroovyStarter.java   |  55 ++--
 .../codehaus/groovy/tools/LoaderConfiguration.java |  16 +-
 .../java/org/codehaus/groovy/tools/RootLoader.java |  98 +++---
 .../codehaus/groovy/tools/shell/Groovysh.groovy    | 229 ++++++-------
 .../tools/shell/InteractiveShellRunner.groovy      | 100 +++---
 .../codehaus/groovy/tools/shell/Interpreter.groovy |  70 ++--
 .../org/codehaus/groovy/tools/shell/Main.groovy    |  25 +-
 .../shell/util/DefaultCommandsRegistrar.groovy     |  57 ++--
 .../groovy/tools/shell/CompletorTestSupport.groovy |  41 +--
 .../groovy/tools/shell/ErrorDisplayTest.groovy     |  51 ++-
 .../groovy/tools/shell/ImportCompletorTest.groovy  |  22 +-
 .../groovy/tools/shell/ShellRunnerTest.groovy      |  86 +++--
 .../tools/shell/ShellRunnerTestSupport.groovy      |  34 +-
 .../completion/GroovySyntaxCompletorTest.groovy    | 361 ++++++++-------------
 14 files changed, 558 insertions(+), 687 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java b/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
index 08f8f1cf08..a76ed224ee 100644
--- a/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
+++ b/src/main/java/org/codehaus/groovy/tools/GroovyStarter.java
@@ -19,10 +19,10 @@
 package org.codehaus.groovy.tools;
 
 import java.io.FileInputStream;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Arrays;
 
 /**
  * Helper class to initialize the Groovy runtime.
@@ -33,12 +33,19 @@ public class GroovyStarter {
         System.out.println("possible programs are 'groovyc','groovy','console', and 'groovysh'");
         System.exit(1);
     }
-    
-    
-    public static void rootLoader(String args[]) {
+
+    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();
-        
+
         // evaluate parameters
         boolean hadMain=false, hadConf=false, hadCP=false;
         int argsOffset = 0;
@@ -69,7 +76,7 @@ public class GroovyStarter {
                 hadConf=true;
             } else {
                 break;
-            }            
+            }
         }
 
         // this allows to override the commandline conf
@@ -80,10 +87,9 @@ public class GroovyStarter {
         if (lc.getMainClass()==null && conf==null) {
             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 {
@@ -95,39 +101,32 @@ public class GroovyStarter {
         }
         // create loader and execute main class
         ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<RootLoader>() {
+            @Override
             public RootLoader run() {
                 return new RootLoader(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);
         }
     }
-    
+
     private static void exit(Exception e) {
         e.printStackTrace();
         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 a95258c807..d36f92eb55 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,12 +141,17 @@ 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, ","));
+            StringBuilder sb = new StringBuilder();
+            for (String configScript : configScripts) {
+                if (sb.length() > 0) sb.append(',');
+                sb.append(configScript);
+            }
+            System.setProperty("groovy.starter.configscripts", sb.toString());
         }
     }
 
     /*
-    * Expands the properties inside the given string to it's values.
+    * Expands the properties inside the given string to their values.
     */
     private static String assignProperties(String str) {
         int propertyIndexStart = 0, propertyIndexEnd = 0;
@@ -170,7 +174,7 @@ public class LoaderConfiguration {
             if (propertyIndexStart == -1) break;
             result.append(str, propertyIndexEnd, propertyIndexStart);
 
-            propertyIndexEnd = str.indexOf("}", propertyIndexStart);
+            propertyIndexEnd = str.indexOf('}', propertyIndexStart);
             if (propertyIndexEnd == -1) break;
 
             String propertyKey = str.substring(propertyIndexStart + 2, propertyIndexEnd);
@@ -192,9 +196,9 @@ public class LoaderConfiguration {
         }
 
         if (propertyIndexStart == -1 || propertyIndexStart >= str.length()) {
-            result.append(str.substring(propertyIndexEnd));
+            result.append(str, propertyIndexEnd, str.length());
         } else if (propertyIndexEnd == -1) {
-            result.append(str.substring(propertyIndexStart));
+            result.append(str, propertyIndexStart, str.length());
         }
 
         return result.toString();
diff --git a/src/main/java/org/codehaus/groovy/tools/RootLoader.java b/src/main/java/org/codehaus/groovy/tools/RootLoader.java
index dfd42ad7c2..9d4e3f853d 100644
--- a/src/main/java/org/codehaus/groovy/tools/RootLoader.java
+++ b/src/main/java/org/codehaus/groovy/tools/RootLoader.java
@@ -23,12 +23,11 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /**
  * This ClassLoader should be used as root of class loaders. Any
- * RootLoader does have it's own classpath. When searching for a
+ * RootLoader does have its own classpath. When searching for a
  * class or resource this classpath will be used. Parent
  * Classloaders are ignored first. If a class or resource
  * can't be found in the classpath of the RootLoader, then parent is
@@ -45,7 +44,7 @@ import java.util.Map;
  * classloader does know a class which depends on a class only
  * a child of this loader does know, then you won't be able to
  * load the class. To load the class the child is not allowed
- * to redirect it's search for the class to the parent first.
+ * to redirect its search for the class to the parent first.
  * That way the child can load the class. If the child does not
  * have all classes to do this, this fails of course.
  * <p>
@@ -73,58 +72,61 @@ 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 */ }
+        } catch (Exception e) {
+            // ignore
+        }
     }
 
     private static ClassLoader chooseParent() {
         ClassLoader cl = RootLoader.class.getClassLoader();
-        if (cl != null) return cl;
-        return ClassLoader.getSystemClassLoader();
+        if (cl == null)
+            cl = ClassLoader.getSystemClassLoader();
+        return cl;
     }
 
     /**
-     * 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) {
+    public RootLoader(final LoaderConfiguration lc) {
         this(chooseParent());
+
         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,47 +136,53 @@ public class RootLoader extends URLClassLoader {
     }
 
     /**
-     * loads a class using the name of the class
+     * {@inheritDoc}
      */
-    protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
-        Class c = this.findLoadedClass(name);
+    @Override
+    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}
      */
-    public URL getResource(String name) {
+    @Override
+    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}
      */
-    public void addURL(URL url) {
+    @Override
+    public void addURL(final URL url) {
         super.addURL(url);
     }
 
-    private Class oldFindClass(String name) throws ClassNotFoundException {
-        return super.findClass(name);
-    }
-
-    protected Class findClass(String name) throws ClassNotFoundException {
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected Class<?> findClass(final String name) throws ClassNotFoundException {
         throw new ClassNotFoundException(name);
     }
 }
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Groovysh.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Groovysh.groovy
index db152a8371..901f8fbe15 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Groovysh.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Groovysh.groovy
@@ -19,6 +19,8 @@
 package org.codehaus.groovy.tools.shell
 
 import antlr.TokenStreamException
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
 import jline.Terminal
 import jline.WindowsTerminal
@@ -44,19 +46,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_]+\\(.*'
 
@@ -71,6 +73,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()
 
@@ -87,57 +92,54 @@ 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
 
-    Groovysh(final ClassLoader classLoader, final Binding binding, final IO io, final Closure registrar) {
-        this(classLoader, binding, io, registrar, null)
+    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, 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)
     }
 
-    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, Closure registrar = null, CompilerConfiguration configuration = null) {
+        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() {
@@ -152,11 +154,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
         }
 
@@ -228,16 +230,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
@@ -254,7 +255,7 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
             })
         }
         // Evaluate the current buffer w/imports and dummy statement
-        List<String> buff;
+        List<String> buff
         if (variableBlocks) {
             buff = [importsSpec] + ['try {', 'true'] + current + ['} finally {' + variableBlocks + '}']
         } else {
@@ -263,23 +264,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 ->
@@ -297,16 +296,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())
@@ -340,27 +338,27 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
 
         // not sure whether the same Lexer instance could be reused.
         def lexer = CurlyCountingGroovyLexer.createGroovyLexer(src.toString())
-
         // read all tokens
         try {
-            while (lexer.nextToken().getType() != CurlyCountingGroovyLexer.EOF) {}
+            while (lexer.nextToken().getType() != CurlyCountingGroovyLexer.EOF) {
+            }
         } catch (TokenStreamException e) {
-            // pass
+            // ignore
         }
-        int parenIndent = (lexer.getParenLevel()) * indentSize
+        int curlyIndent = lexer.getParenLevel() * indentSize
 
         // dedent after closing brackets
-        return ' ' * Math.max(parenIndent, 0)
+        return ' ' * Math.max(curlyIndent, 0)
     }
 
-    public String renderPrompt() {
-        return prompt.render( buildPrompt() )
+    String renderPrompt() {
+        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
@@ -378,29 +376,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 {
@@ -413,30 +407,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)
         }
     }
 
@@ -444,7 +438,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,
@@ -455,12 +449,12 @@ 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')
         }
 
-        resultHook.call((Object)result)
+        resultHook.call(result)
 
         interp.context['_'] = result
 
@@ -470,16 +464,16 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     final Closure defaultErrorHook = { Throwable cause ->
         assert cause != null
 
-        if (log.debug || ! (cause instanceof CompilationFailedException)) {
+        if (log.debug || !(cause instanceof CompilationFailedException)) {
             // For CompilationErrors, the Exception Class is usually not useful to the user
             io.err.println("@|bold,red ERROR|@ ${cause.getClass().name}:")
         }
 
         if (cause instanceof MultipleCompilationErrorsException) {
-            Writer data = new org.apache.groovy.io.StringBuilderWriter();
-            PrintWriter writer = new PrintWriter(data);
+            Writer data = new org.apache.groovy.io.StringBuilderWriter()
+            PrintWriter writer = new PrintWriter(data)
             ErrorCollector collector = ((MultipleCompilationErrorsException) cause).getErrorCollector()
-            Iterator<Message> msgIterator = collector.getErrors().iterator()
+            def msgIterator = (Iterator<Message>) collector.getErrors().iterator()
             while (msgIterator.hasNext()) {
                 Message errorMsg = msgIterator.next()
                 errorMsg.write(writer)
@@ -491,7 +485,6 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
         } else {
             io.err.println("@|bold,red ${cause.message}|@")
 
-
             maybeRecordError(cause)
 
             if (log.debug) {
@@ -500,13 +493,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()
 
@@ -542,13 +530,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')
         }
@@ -563,38 +551,36 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
     }
 
     /**
-    * Run Interactive Shell with optional initial script and files to load
-    */
-    int run(final String evalString, final List<String> filenames) {
+     * Run the Interactive Shell with optional initial script and files to load.
+     */
+    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')
             }
 
@@ -611,18 +597,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
@@ -630,12 +612,11 @@ try {$COLLECTED_BOUND_VARS_MAP_VARNAME[\"$varname\"] = $varname;
         return code
     }
 
-
     /**
      * maybe displays log information and a welcome message
      * @param term
      */
-    public void displayWelcomeBanner(InteractiveShellRunner runner) {
+    void displayWelcomeBanner(InteractiveShellRunner runner) {
         if (!log.debug && io.quiet) {
             // nothing to do here
             return
diff --git a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/InteractiveShellRunner.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/InteractiveShellRunner.groovy
index 0733ec9bc7..ee16251c3d 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/InteractiveShellRunner.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/InteractiveShellRunner.groovy
@@ -18,9 +18,13 @@
  */
 package org.codehaus.groovy.tools.shell
 
+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
+import jline.console.completer.Completer
 import jline.console.completer.CompletionHandler
 import jline.console.history.FileHistory
 import org.codehaus.groovy.tools.shell.completion.CustomClassSyntaxCompletor
@@ -37,55 +41,54 @@ import org.codehaus.groovy.tools.shell.util.WrappedInputStream
 /**
  * Support for running a {@link Shell} interactively using the JLine library.
  */
-class InteractiveShellRunner
-    extends ShellRunner
-    implements Runnable
-{
-    ConsoleReader reader
+@AutoFinal @CompileStatic
+class InteractiveShellRunner extends ShellRunner implements Runnable {
 
+    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()
 
-        CustomClassSyntaxCompletor classnameCompletor = new CustomClassSyntaxCompletor(shell)
+        def classnameCompletor = new CustomClassSyntaxCompletor(shell)
 
-        reader.addCompleter(new GroovySyntaxCompletor(shell,
+        reader.addCompleter(completer)
+        reader.addCompleter(new GroovySyntaxCompletor(
+                shell,
                 new ReflectionCompletor(shell),
                 classnameCompletor,
-                [new KeywordSyntaxCompletor(),
+                [
+                        new KeywordSyntaxCompletor(),
                         new VariableSyntaxCompletor(shell),
                         classnameCompletor,
-                        new ImportsSyntaxCompletor(shell)],
-                new FileNameCompleter(false)))
+                        new ImportsSyntaxCompletor(shell)
+                ],
+                new FileNameCompleter(false)
+        ))
     }
 
     @Override
     void run() {
-        for (Command command in shell.registry.commands()) {
+        for (command in shell.registry.commands()) {
             completer.add(command)
         }
 
@@ -97,20 +100,7 @@ class InteractiveShellRunner
         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))) {
@@ -135,18 +125,18 @@ class InteractiveShellRunner
 
     @Override
     protected boolean work() {
-        boolean result= super.work()
+        boolean result = super.work()
         adjustHistory()
 
         result
     }
 
     private void adjustHistory() {
-        // we save the evicted line in casesomeone wants to use it with history recall
+        // 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()
@@ -155,21 +145,33 @@ class InteractiveShellRunner
         }
     }
 
+    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 = []
+    List<Completer> list = []
 
-    private boolean dirty = false
+    private boolean dirty
 
-    def add(final Command command) {
+    def add(Command command) {
         assert command
 
         //
@@ -196,7 +198,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/codehaus/groovy/tools/shell/Interpreter.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Interpreter.groovy
index bb0bd1d826..1304071b20 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Interpreter.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Interpreter.groovy
@@ -18,29 +18,31 @@
  */
 package org.codehaus.groovy.tools.shell
 
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
 import org.codehaus.groovy.control.CompilerConfiguration
 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
-{
+@AutoFinal @CompileStatic
+class Interpreter implements Evaluator {
+
     static final String SCRIPT_FILENAME = 'groovysh_evaluate'
 
-    private final Logger log = Logger.create(this.class)
+    private final Logger log = Logger.create(getClass())
 
     private final GroovyShell shell
 
-    Interpreter(final ClassLoader classLoader, final Binding binding) {
-        this(classLoader, binding, null)
-    }
-
-    Interpreter(final ClassLoader classLoader, final Binding binding, CompilerConfiguration configuration) {
+    Interpreter(ClassLoader classLoader, Binding binding, CompilerConfiguration configuration = null) {
         assert classLoader
         assert binding
         if (configuration != null) {
@@ -50,35 +52,36 @@ class Interpreter implements Evaluator
         }
     }
 
+    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()
             }
 
@@ -86,28 +89,23 @@ class Interpreter implements Evaluator
             log.debug("Evaluation result: ${InvokerHelper.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/codehaus/groovy/tools/shell/Main.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy
index 8957a67ca3..8470137be6 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/Main.groovy
@@ -20,6 +20,9 @@ package org.codehaus.groovy.tools.shell
 
 import groovy.cli.picocli.CliBuilder
 import groovy.cli.picocli.OptionAccessor
+import groovy.transform.AutoFinal
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
 import jline.TerminalFactory
 import jline.UnixTerminal
 import jline.UnsupportedTerminal
@@ -46,6 +49,7 @@ import static org.apache.groovy.util.SystemUtil.setSystemPropertyFrom
  *
  * Main CLI entry-point for <tt>groovysh</tt>.
  */
+@AutoFinal @CompileStatic
 class Main {
     final Groovysh groovysh
 
@@ -69,7 +73,8 @@ class Main {
      * create a Main instance, configures it according to CLI arguments, and starts the shell.
      * @param main must have a Groovysh member that has an IO member.
      */
-    static void main(final String[] args) {
+    @CompileDynamic
+    static void main(String[] args) {
         MessageSource messages = new MessageSource(Main)
         CliBuilder cli = new CliBuilder(usage: 'groovysh [options] [...]', stopAtNonOption: false,
                 header: messages['cli.option.header'])
@@ -147,10 +152,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)
     }
@@ -181,10 +189,8 @@ class Main {
             }
         }
 
-
-        SecurityManager psm = System.getSecurityManager()
+        final SecurityManager psm = System.getSecurityManager()
         System.setSecurityManager(new NoExitSecurityManager())
-
         try {
             code = shell.run(evalString, filenames)
         }
@@ -203,6 +209,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
 
@@ -230,6 +237,7 @@ class Main {
                 // Should never happen
                 throw new IllegalArgumentException("Invalid Terminal type: $type")
         }
+
         if (enableAnsi) {
             installAnsi() // must be called before IO(), since it modifies System.in
             Ansi.enabled = !suppressColor
@@ -252,8 +260,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/codehaus/groovy/tools/shell/util/DefaultCommandsRegistrar.groovy b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/util/DefaultCommandsRegistrar.groovy
index af5b6622af..1d9deefb30 100644
--- a/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/util/DefaultCommandsRegistrar.groovy
+++ b/subprojects/groovy-groovysh/src/main/groovy/org/codehaus/groovy/tools/shell/util/DefaultCommandsRegistrar.groovy
@@ -40,49 +40,44 @@ import org.codehaus.groovy.tools.shell.commands.SetCommand
 import org.codehaus.groovy.tools.shell.commands.ShowCommand
 
 /**
- * Registers {@link Command} classes from an XML file like:
- * <commands>
- *  <command>org.codehaus.groovy.tools.shell.commands.HelpCommand</command>
- * ...
- * </commands>
+ * Registers default {@link Command} instances.
  */
-class DefaultCommandsRegistrar
-{
+class DefaultCommandsRegistrar {
 
     private final Shell shell
 
     DefaultCommandsRegistrar(final Shell shell) {
         assert shell != null
-
         this.shell = shell
     }
 
     void register() {
+        List<Command> commands = [
+            new HelpCommand(shell),
+            new ExitCommand(shell),
+            new ImportCommand(shell),
+            new DisplayCommand(shell),
+            new ClearCommand(shell),
+            new ShowCommand(shell),
+            new InspectCommand(shell),
+            new PurgeCommand(shell),
+            new EditCommand(shell),
+            new LoadCommand(shell),
+            new SaveCommand(shell),
+            new RecordCommand(shell),
+            new HistoryCommand(shell),
+            new AliasCommand(shell),
+            new SetCommand(shell),
+            new GrabCommand(shell),
+            new RegisterCommand(shell),
+        ]
 
-        for (Command classname in [
-                new HelpCommand(shell),
-                new ExitCommand(shell),
-                new ImportCommand(shell),
-                new DisplayCommand(shell),
-                new ClearCommand(shell),
-                new ShowCommand(shell),
-                new InspectCommand(shell),
-                new PurgeCommand(shell),
-                new EditCommand(shell),
-                new LoadCommand(shell),
-                new SaveCommand(shell),
-                new RecordCommand(shell),
-                new HistoryCommand(shell),
-                new AliasCommand(shell),
-                new SetCommand(shell),
-                new GrabCommand(shell),
-                // does not do anything
-                //new ShadowCommand(shell),
-                new RegisterCommand(shell),
-                new DocCommand(shell)
-        ]) {
-            shell.register(classname)
+        if (!Boolean.getBoolean('groovysh.disableDocCommand')) {
+            commands.add(new DocCommand(shell))
         }
 
+        for (command in commands) {
+            shell.register(command)
+        }
     }
 }
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/CompletorTestSupport.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/CompletorTestSupport.groovy
index 7b5db874c7..31973b07c5 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/CompletorTestSupport.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/CompletorTestSupport.groovy
@@ -21,53 +21,36 @@ package org.codehaus.groovy.tools.shell
 import groovy.mock.interceptor.MockFor
 import org.codehaus.groovy.tools.shell.completion.IdentifierCompletor
 import org.codehaus.groovy.tools.shell.completion.ReflectionCompletor
-import org.codehaus.groovy.tools.shell.util.PackageHelper
 import org.codehaus.groovy.tools.shell.util.PackageHelperImpl
 
 abstract class CompletorTestSupport extends GroovyTestCase {
 
-    BufferManager bufferManager = new BufferManager()
-    IO testio
     ByteArrayOutputStream mockOut
     ByteArrayOutputStream mockErr
-    MockFor groovyshMocker
-    MockFor packageHelperMocker
-    PackageHelper mockPackageHelper
+    IO testio
 
     MockFor reflectionCompletorMocker
-
+    MockFor packageHelperMocker
     MockFor idCompletorMocker
+    MockFor groovyshMocker
 
     @Override
-    void setUp() {
+    protected void setUp() {
         super.setUp()
         mockOut = new ByteArrayOutputStream()
-
         mockErr = new ByteArrayOutputStream()
+        testio = new IO(new ByteArrayInputStream(), mockOut, mockErr)
 
-        testio = new IO(
-                new ByteArrayInputStream(),
-                mockOut,
-                mockErr)
         reflectionCompletorMocker = new MockFor(ReflectionCompletor)
-
+        packageHelperMocker = new MockFor(PackageHelperImpl)
         idCompletorMocker = new MockFor(IdentifierCompletor)
 
         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/codehaus/groovy/tools/shell/ErrorDisplayTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ErrorDisplayTest.groovy
index 6245048b8b..0ba00d9338 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ErrorDisplayTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ErrorDisplayTest.groovy
@@ -18,56 +18,43 @@
  */
 package org.codehaus.groovy.tools.shell
 
-import jline.console.ConsoleReader
 import jline.console.completer.CandidateListCompletionHandler
 
-
-class ErrorDisplayTest extends ShellRunnerTestSupport {
+final 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/codehaus/groovy/tools/shell/ImportCompletorTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ImportCompletorTest.groovy
index 7104d444db..c7d5eca445 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ImportCompletorTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ImportCompletorTest.groovy
@@ -17,22 +17,23 @@
  *  under the License.
  */
 package org.codehaus.groovy.tools.shell
+
 import groovy.mock.interceptor.MockFor
-import jline.console.completer.ArgumentCompleter
 import jline.console.completer.Completer
-import jline.console.completer.NullCompleter
-import jline.console.completer.StringsCompleter
 import org.codehaus.groovy.tools.shell.commands.ImportCommand
 import org.codehaus.groovy.tools.shell.commands.ImportCompleter
 import org.codehaus.groovy.tools.shell.util.PackageHelper
 import org.codehaus.groovy.tools.shell.util.Preferences
+
 /**
  * as opposed to MockFor, traditional custom mocking allows @CompileStatic for the class under Test
  */
 class MockPackageHelper implements PackageHelper {
+
     Set<String> mockContents
+
     MockPackageHelper(Collection<String> mockContents) {
-        this.mockContents = new HashSet<String>(mockContents)
+        this.mockContents = mockContents.toSet()
     }
 
     @Override
@@ -41,16 +42,16 @@ class MockPackageHelper implements PackageHelper {
     }
 
     @Override
-    void reset() { }
+    void reset() {
+    }
 }
 
-class ImportCompleterUnitTest extends GroovyTestCase {
+final class ImportCompleterUnitTest extends GroovyTestCase {
 
     private MockFor preferencesMocker
 
-
     @Override
-    void setUp() {
+    protected void setUp() {
         super.setUp()
         preferencesMocker = new MockFor(Preferences)
     }
@@ -138,7 +139,6 @@ class ImportCompleterUnitTest extends GroovyTestCase {
         assert !'java.util.T'.matches(ImportCompleter.LOWERCASE_IMPORT_ITEM_PATTERN)
     }
 
-
     private void assertCompletionCandidatesMatch(
             final PackageHelper packageHelper,
             final String buffer,
@@ -278,7 +278,9 @@ class ImportCompleterUnitTest extends GroovyTestCase {
     }
 }
 
-class ImportCompleterTest extends CompletorTestSupport {
+final class ImportCompleterTest extends CompletorTestSupport {
+
+    private PackageHelper mockPackageHelper
 
     void testEmpty() {
         mockPackageHelper = new MockPackageHelper(['java', 'test'])
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTest.groovy
index 41715d8392..cd92f26518 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTest.groovy
@@ -23,45 +23,51 @@ import jline.console.ConsoleReader
 import jline.console.completer.CandidateListCompletionHandler
 import org.codehaus.groovy.tools.shell.util.Preferences
 
-class ShellRunnerTest extends GroovyTestCase {
+final 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())
         }}
     }
 
     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())
         }}
     }
@@ -70,13 +76,13 @@ 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())
         }}
     }
@@ -84,51 +90,39 @@ class ShellRunnerTest extends GroovyTestCase {
     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
-    }
 }
 
 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())
         }}
     }
diff --git a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTestSupport.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTestSupport.groovy
index d74c6f38e0..16669c362a 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTestSupport.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/ShellRunnerTestSupport.groovy
@@ -23,41 +23,39 @@ import groovy.mock.interceptor.StubFor
 import jline.console.ConsoleReader
 
 /**
- * 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 {}
@@ -66,7 +64,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/codehaus/groovy/tools/shell/completion/GroovySyntaxCompletorTest.groovy b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/completion/GroovySyntaxCompletorTest.groovy
index b7df08779e..0abe1d8ab4 100644
--- a/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/completion/GroovySyntaxCompletorTest.groovy
+++ b/subprojects/groovy-groovysh/src/test/groovy/org/codehaus/groovy/tools/shell/completion/GroovySyntaxCompletorTest.groovy
@@ -19,205 +19,156 @@
 package org.codehaus.groovy.tools.shell.completion
 
 import groovy.mock.interceptor.MockFor
+import org.codehaus.groovy.tools.shell.BufferManager
 import org.codehaus.groovy.tools.shell.CommandRegistry
 import org.codehaus.groovy.tools.shell.CompletorTestSupport
 import org.codehaus.groovy.tools.shell.Groovysh
-import org.codehaus.groovy.tools.shell.commands.ImportCommand
 
-class GroovySyntaxCompletorTest extends CompletorTestSupport {
+final class GroovySyntaxCompletorTest extends CompletorTestSupport {
 
-    void testEmpty() {
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.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()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            assert -1 == completor.complete('', 0, [])
+            Groovysh groovysh = new Groovysh()
+            IdentifierCompletor identifierCompletor = idCompletorMocker.proxyDelegateInstance()
+            ReflectionCompletor reflectionCompletor = reflectionCompletorMocker.proxyInstance(groovysh)
+            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovysh, reflectionCompletor, identifierCompletor, [identifierCompletor], fileNameCompleter)
+
+            result = completor.complete(buffer, cursor, candidates)
         }
+        return result
+    }
+
+    void testEmpty() {
+        int result = runTest('')
+
+        assert result == -1
+        assert candidates.isEmpty()
     }
 
     void testIdentifier() {
         idCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['jav']); candidates << 'javup'; candidates << 'java.lang.String' ; true}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'jav'
-            // in the shell, only Classes in the default package occur,but well...
-            assert 0 == completor.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() {
         idCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['{', 'jav']); candidates << 'javup'; candidates << 'java.lang.String' ; true}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = '{jav'
-            // in the shell, only Classes in the default package occur,but well...
-            assert 1 == completor.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() {
         reflectionCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Math', '.', 'ma']); candidates << 'max('; 5}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'Math.ma'
-            assert 5 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['max('] == candidates
-        }
+
+        int result = runTest('Math.ma')
+
+        assert result == 5
+        assert candidates == ['max(']
     }
 
     void testMemberOptionalDot() {
         reflectionCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Math', '?.', 'ma']); candidates << 'max('; 6}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'Math?.ma'
-            assert 6 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['max('] == candidates
-        }
+
+        int result = runTest('Math?.ma')
+
+        assert result == 6
+        assert candidates == ['max(']
     }
 
     void testMemberSpreadDot() {
         reflectionCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['[', 'foo', ']', '*.', 'len']); candidates << 'length()'; 9}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = '[\'foo\']*.len'
-            assert 9 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['length()'] == candidates
-        }
+
+        int result = runTest('[\'foo\']*.len')
+
+        assert result == 9
+        assert candidates == ['length()']
     }
 
     void testMemberAfterMethod() {
         reflectionCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Fo', '.', 'ba', '(', ')', '.', 'xyz']); candidates << 'xyzabc'; 0}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'Fo.ba().xyz'
-            // xyz cannot be not a var here
-            assert 0 == completor.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() {
         idCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Foo', '.', 'bar', '(', 'xyz']); candidates << 'xyzabc'; true}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'Foo.bar(xyz'
-            assert 8 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['xyzabc'] == candidates
-        }
+
+        int result = runTest('Foo.bar(xyz')
+
+        assert result == 8
+        assert candidates == ['xyzabc']
     }
 
     void testIndentifierAfterParensBeforeDot() {
         idCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['Foo', '.', 'bar', '(', 'xyz']); candidates << 'xyzabc'; true}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            // cursor is BEFORE dot
-            assert 8 == completor.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() {
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'String jav'
-            assert -1 == completor.complete(buffer, buffer.length(), candidates)
-            assert [] == candidates
-        }
+        int result = runTest('String jav')
+
+        assert result == -1
+        assert candidates == []
     }
 
     void testInfixKeyword() {
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'class Foo ext'
-            assert 10 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['extends'] == candidates
-        }
+        int result = runTest('class Foo ext')
+
+        assert result == 10
+        assert candidates == ['extends']
     }
 
     void testInstanceof() {
         idCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['x', 'instanceof', 'P']); candidates << 'Property'; 13}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
 
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'x instanceof P'
-            assert 13 == completor.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 reflectionCompletor has to deal with this
         reflectionCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['deletehardDisk', '(', ')', ';', 'foo', '.', 'subs']); candidates << 'substring('; 22}
 
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        // mock doing the right thing
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'deletehardDisk(); foo.subs'
-            assert 22 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['substring('] == candidates
-        }
+        int result = runTest('deletehardDisk(); foo.subs')
+
+        assert result == 22
+        assert candidates == ['substring(']
     }
 
     void testAfterOperator() {
@@ -225,106 +176,76 @@ class GroovySyntaxCompletorTest extends CompletorTestSupport {
         reflectionCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['a', '=', 'foo', '.', 'subs']); candidates << 'substring('; 9}
 
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        // mock doing the right thing
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'a = foo.subs'
-            assert 9 == completor.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()
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        // mock asserting nothing gets evaluated
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            // import command prevents reflection completion
-            registry.register(new ImportCommand(groovyshMock))
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'import foo'
-            assert -1 == completor.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?
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        // mock asserting GString is not evaluated
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = '"\${foo.delete()}".subs'
-            assert candidates == [] && -1 == completor.complete(buffer, buffer.length(), candidates)
-        }
+        int result = runTest('"\${foo.delete()}".subs') // GString not evaluated
+
+        assert result == -1
+        assert candidates == []
     }
 
     void testInStringFilename() {
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        MockFor filenameCompletorMocker = 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
-        filenameCompletorMocker.demand.complete(1) {bufferline, cursor, candidates ->
-            assert(bufferline == pathstart)
-            assert(cursor == pathstart.length())
-            candidates << 'foobar'; 5}
-        groovyshMocker.use { filenameCompletorMocker.use {
-            FileNameCompleter mockFileComp = new FileNameCompleter()
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], mockFileComp)
-            def candidates = []
-            assert 'foo(\'/usr/'.length() == completor.complete(buffer, buffer.length(), candidates)
-            assert ['foobar'] == candidates
-        }}
+
+        filenameCompleterMocker.demand.complete(1) { bufferline, cursor, candidates ->
+            assert bufferline == pathstart
+            assert cursor == pathstart.length()
+            candidates << 'foobar'
+            5
+        }
+        filenameCompleterMocker.use {
+            String buffer = linestart + pathstart
+            int result = runTest(buffer, buffer.length(), new FileNameCompleter())
+
+            assert result == "foo('/usr/".length()
+            assert candidates == ['foobar']
+        }
     }
 
     void testInStringFilenameBlanks() {
-        // test with blanks (non-tokens) before the first hyphen
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        MockFor filenameCompletorMocker = 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
-        filenameCompletorMocker.demand.complete(1) {bufferline, cursor, candidates ->
+
+        filenameCompleterMocker.demand.complete(1) {bufferline, cursor, candidates ->
             assert bufferline == pathstart
             assert cursor == pathstart.length()
-            candidates << 'foobar'; 5}
-        groovyshMocker.use { filenameCompletorMocker.use {
-            FileNameCompleter mockFileComp = new FileNameCompleter()
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], mockFileComp)
-            def candidates = []
-            assert 'x = \'/usr/'.length() == completor.complete(buffer, buffer.length(), candidates)
-            assert ['foobar'] == candidates
-        }}
+            candidates << 'foobar'
+            5
+        }
+        filenameCompleterMocker.use {
+            String buffer = linestart + pathstart
+            int result = runTest(buffer, buffer.length(), new FileNameCompleter())
+
+            assert result == "x = '/usr/".length()
+            assert candidates == ['foobar']
+        }
     }
 
     void testInGString() {
         idCompletorMocker.demand.complete(1) { tokens, candidates ->
             assert(tokens*.text == ['', '{', 'foo']); candidates << 'foobar'; true}
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        // mock asserting GString is not evaluated
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = '"\${foo'
-            assert 3 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['foobar'] == candidates
-        }
+
+        int result = runTest('"\${foo')
+
+        assert result == 3
+        assert candidates == ['foobar']
     }
 
     void testMultilineComplete() {
@@ -332,16 +253,10 @@ class GroovySyntaxCompletorTest extends CompletorTestSupport {
             assert(tokens*.text == ['xyz\nabc', '.', 'subs']); candidates << 'substring('; 7}
         bufferManager.buffers.add(['"""xyz'])
         bufferManager.setSelected(1)
-        IdentifierCompletor mockIdCompletor = idCompletorMocker.proxyDelegateInstance()
-        groovyshMocker.use {
-            Groovysh groovyshMock = new Groovysh()
-            ReflectionCompletor mockReflComp = reflectionCompletorMocker.proxyInstance(groovyshMock)
-            GroovySyntaxCompletor completor = new GroovySyntaxCompletor(groovyshMock, mockReflComp, mockIdCompletor, [mockIdCompletor], null)
-            def candidates = []
-            String buffer = 'abc""".subs'
-            assert 7 == completor.complete(buffer, buffer.length(), candidates)
-            assert ['substring('] == candidates
-        }
+
+        int result = runTest('abc""".subs')
+
+        assert result == 7
+        assert candidates == ['substring(']
     }
 }
-