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/07/06 16:45:37 UTC

[groovy] 01/02: minor refactor

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

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

commit a8823a4821143978b2bfca5654819cf5a9e02289
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Jul 6 10:34:14 2022 -0500

    minor refactor
---
 .../apache/groovy/ast/tools/ExpressionUtils.java   | 257 ++++-----
 src/test/gls/annotations/AnnotationTest.groovy     | 629 ++++++++++-----------
 2 files changed, 430 insertions(+), 456 deletions(-)

diff --git a/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java b/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
index a5f8813463..373f8a283a 100644
--- a/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
+++ b/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
@@ -34,8 +34,9 @@ import org.codehaus.groovy.runtime.typehandling.NumberMath;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
+import java.util.List;
+import java.util.ListIterator;
 
-import static org.codehaus.groovy.ast.ClassHelper.isStringType;
 import static org.codehaus.groovy.ast.ClassHelper.isWrapperByte;
 import static org.codehaus.groovy.ast.ClassHelper.isWrapperCharacter;
 import static org.codehaus.groovy.ast.ClassHelper.isWrapperDouble;
@@ -57,20 +58,62 @@ import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
 
 public final class ExpressionUtils {
 
-    // NOTE: values are sorted in ascending order
+    public static boolean isNullConstant(final Expression expression) {
+        return expression instanceof ConstantExpression && ((ConstantExpression) expression).isNullExpression();
+    }
+
+    public static boolean isThisExpression(final Expression expression) {
+        return expression instanceof VariableExpression && ((VariableExpression) expression).isThisExpression();
+    }
+
+    public static boolean isSuperExpression(final Expression expression) {
+        return expression instanceof VariableExpression && ((VariableExpression) expression).isSuperExpression();
+    }
+
+    public static boolean isThisOrSuper(final Expression expression) {
+        return isThisExpression(expression) || isSuperExpression(expression);
+    }
+
+    /**
+     * Determines if a type matches another type (or array thereof).
+     *
+     * @param targetType the candidate type
+     * @param type the type we are checking against
+     * @param recurse true if we can have multi-dimension arrays; should be false for annotation member types
+     * @return true if the type equals the targetType or array thereof
+     */
+    public static boolean isTypeOrArrayOfType(final ClassNode targetType, final ClassNode type, final boolean recurse) {
+        if (targetType == null) return false;
+        if (type.equals(targetType)) return true;
+        return (targetType.isArray() && recurse ? isTypeOrArrayOfType(targetType.getComponentType(), type, recurse) : type.equals(targetType.getComponentType()));
+    }
+
+    /**
+     * Determines if a type is derived from Number (or array thereof).
+     *
+     * @param targetType the candidate type
+     * @param recurse true if we can have multi-dimension arrays; should be false for annotation member types
+     * @return true if the type equals the targetType or array thereof
+     */
+    public static boolean isNumberOrArrayOfNumber(final ClassNode targetType, final boolean recurse) {
+        if (targetType == null) return false;
+        if (targetType.isDerivedFrom(ClassHelper.Number_TYPE)) return true;
+        return (targetType.isArray() && recurse ? isNumberOrArrayOfNumber(targetType.getComponentType(), recurse) : targetType.isArray() && targetType.getComponentType().isDerivedFrom(ClassHelper.Number_TYPE));
+    }
+
+    //--------------------------------------------------------------------------
+
     private static final int[] HANDLED_TYPES = {
             PLUS, MINUS, MULTIPLY, DIVIDE, POWER,
-            LEFT_SHIFT, RIGHT_SHIFT, RIGHT_SHIFT_UNSIGNED,
-            BITWISE_OR, BITWISE_AND, BITWISE_XOR
+            BITWISE_OR, BITWISE_AND, BITWISE_XOR,
+            LEFT_SHIFT, RIGHT_SHIFT, RIGHT_SHIFT_UNSIGNED
     };
     static {
         Arrays.sort(HANDLED_TYPES);
     }
 
-    private ExpressionUtils() { }
-
     /**
-     * Turns expressions of the form ConstantExpression(40) + ConstantExpression(2)
+     * Converts expressions like ConstantExpression(40) + ConstantExpression(2)
      * into the simplified ConstantExpression(42) at compile time.
      *
      * @param be the binary expression
@@ -98,7 +141,7 @@ public final class ExpressionUtils {
                 Expression leftX = transformInlineConstants(be.getLeftExpression(), targetType);
                 Expression rightX = transformInlineConstants(be.getRightExpression(), isShift ? ClassHelper.int_TYPE : targetType);
                 if (leftX instanceof ConstantExpression && rightX instanceof ConstantExpression) {
-                    Number left = safeNumber((ConstantExpression) leftX);
+                    Number left  = safeNumber((ConstantExpression) leftX);
                     Number right = safeNumber((ConstantExpression) rightX);
                     if (left == null || right == null) return null;
                     Number result = null;
@@ -164,46 +207,53 @@ public final class ExpressionUtils {
         return null;
     }
 
-    private static Number safeNumber(final ConstantExpression constX) {
-        Object value = constX.getValue();
-        if (value instanceof Number) return (Number) value;
-        return null;
-    }
-
-    private static ConstantExpression configure(final Expression origX, final ConstantExpression newX) {
-        newX.setSourcePosition(origX);
-        return newX;
-    }
-
     /**
-     * Determine if a type matches another type (or array thereof).
+     * Transforms constants that would appear in annotations so they aren't lost.
+     * Subsequent processing determines whether they are valid, this retains the
+     * constant value as a constant expression.
+     * <p>
+     * The attribute values of annotations must be primitive, string, annotation
+     * or enumeration constants. In various places such constants can be seen
+     * during type resolution but won't be readily accessible in later phases,
+     * e.g. they might be embedded into constructor code.
      *
-     * @param targetType the candidate type
-     * @param type the type we are checking against
-     * @param recurse true if we can have multi-dimension arrays; should be false for annotation member types
-     * @return true if the type equals the targetType or array thereof
+     * @param exp the original expression
+     * @return original or transformed expression
      */
-    public static boolean isTypeOrArrayOfType(final ClassNode targetType, final ClassNode type, final boolean recurse) {
-        if (targetType == null) return false;
-        return type.equals(targetType) ||
-                (targetType.isArray() && recurse
-                ? isTypeOrArrayOfType(targetType.getComponentType(), type, recurse)
-                : type.equals(targetType.getComponentType()));
-    }
+    public static Expression transformInlineConstants(final Expression exp) {
+        if (exp instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) exp;
+            if (pe.getObjectExpression() instanceof ClassExpression) {
+                ClassNode clazz = pe.getObjectExpression().getType();
+                FieldNode field = ClassNodeUtils.getField(clazz, pe.getPropertyAsString());
+                if (field != null && field.isStatic() && field.isFinal() && !field.isEnum()
+                        && field.getInitialValueExpression() instanceof ConstantExpression) {
+                    ConstantExpression value = (ConstantExpression) field.getInitialValueExpression();
+                    value = new ConstantExpression(value.getValue());
+                    return configure(exp, value);
+                }
+            }
+        } else if (exp instanceof BinaryExpression) {
+            BinaryExpression be = (BinaryExpression) exp;
+            Expression lhs = transformInlineConstants(be.getLeftExpression());
+            Expression rhs = transformInlineConstants(be.getRightExpression());
+            if (be.getOperation().getType() == PLUS // GROOVY-9855: inline string concat
+                    && lhs instanceof ConstantExpression && rhs instanceof ConstantExpression
+                    && ClassHelper.isStringType(lhs.getType()) && ClassHelper.isStringType(rhs.getType())) {
+                return configure(exp, new ConstantExpression(lhs.getText() + rhs.getText()));
+            }
+            be.setLeftExpression(lhs);
+            be.setRightExpression(rhs);
 
-    /**
-     * Determine if a type is derived from Number (or array thereof).
-     *
-     * @param targetType the candidate type
-     * @param recurse true if we can have multi-dimension arrays; should be false for annotation member types
-     * @return true if the type equals the targetType or array thereof
-     */
-    public static boolean isNumberOrArrayOfNumber(final ClassNode targetType, final boolean recurse) {
-        if (targetType == null) return false;
-        return targetType.isDerivedFrom(ClassHelper.Number_TYPE) ||
-                (targetType.isArray() && recurse
-                ? isNumberOrArrayOfNumber(targetType.getComponentType(), recurse)
-                : targetType.isArray() && targetType.getComponentType().isDerivedFrom(ClassHelper.Number_TYPE));
+        } else if (exp instanceof ListExpression) {
+            List<Expression> list = ((ListExpression) exp).getExpressions();
+            for (ListIterator<Expression> it = list.listIterator(); it.hasNext();) {
+                Expression e = transformInlineConstants(it.next());
+                it.set(e);
+            }
+        }
+
+        return exp;
     }
 
     /**
@@ -222,49 +272,45 @@ public final class ExpressionUtils {
     public static Expression transformInlineConstants(final Expression exp, final ClassNode attrType) {
         if (exp instanceof PropertyExpression) {
             PropertyExpression pe = (PropertyExpression) exp;
-            if (pe.getObjectExpression() instanceof ClassExpression) {
-                ClassExpression ce = (ClassExpression) pe.getObjectExpression();
-                ClassNode type = ce.getType();
-                if (type.isEnum() || !(type.isResolved() || type.isPrimaryClassNode()))
-                    return exp;
-
+            ClassNode type = pe.getObjectExpression().getType();
+            if (pe.getObjectExpression() instanceof ClassExpression && !type.isEnum()) {
                 if (type.isPrimaryClassNode()) {
-                    FieldNode fn = type.redirect().getField(pe.getPropertyAsString());
+                    FieldNode fn = type.getField(pe.getPropertyAsString());
                     if (fn != null && fn.isStatic() && fn.isFinal()) {
-                        Expression ce2 = transformInlineConstants(fn.getInitialValueExpression(), attrType);
-                        if (ce2 != null) {
-                            return ce2;
+                        Expression e = transformInlineConstants(fn.getInitialValueExpression(), attrType);
+                        if (e != null) {
+                            return e;
                         }
                     }
-                } else {
+                } else if (type.isResolved()) {
                     try {
                         Field field = type.redirect().getTypeClass().getField(pe.getPropertyAsString());
                         if (field != null && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
-                            ConstantExpression ce3 = new ConstantExpression(field.get(null), true);
-                            configure(exp, ce3);
-                            return ce3;
+                            ConstantExpression ce = new ConstantExpression(field.get(null), true);
+                            configure(exp, ce);
+                            return ce;
                         }
                     } catch (Exception | LinkageError e) {
                         // ignore, leave property expression in place and we'll report later
                     }
                 }
             }
-        } else if (exp instanceof BinaryExpression) {
-            ConstantExpression ce = transformBinaryConstantExpression((BinaryExpression) exp, attrType);
-            if (ce != null) {
-                return ce;
-            }
         } else if (exp instanceof VariableExpression) {
             VariableExpression ve = (VariableExpression) exp;
             if (ve.getAccessedVariable() instanceof FieldNode) {
                 FieldNode fn = (FieldNode) ve.getAccessedVariable();
                 if (fn.isStatic() && fn.isFinal()) {
-                    Expression ce = transformInlineConstants(fn.getInitialValueExpression(), attrType);
-                    if (ce != null) {
-                        return ce;
+                    Expression e = transformInlineConstants(fn.getInitialValueExpression(), attrType);
+                    if (e != null) {
+                        return e;
                     }
                 }
             }
+        } else if (exp instanceof BinaryExpression) {
+            ConstantExpression ce = transformBinaryConstantExpression((BinaryExpression) exp, attrType);
+            if (ce != null) {
+                return ce;
+            }
         } else if (exp instanceof ListExpression) {
             return transformListOfConstants((ListExpression) exp, attrType);
         }
@@ -297,80 +343,19 @@ public final class ExpressionUtils {
         return origList;
     }
 
-    /**
-     * The attribute values of annotations must be primitive, String or Enum constants.
-     * In various places, such constants can be seen during type resolution but won't be
-     * readily accessible in later phases, e.g. they might be embedded into constructor code.
-     * This method transforms constants that would appear in annotations early so they aren't lost.
-     * Subsequent processing determines whether they are valid, this method simply retains
-     * the constant value as a constant expression.
-     *
-     * @param exp the original expression
-     * @return the converted expression
-     */
-    public static Expression transformInlineConstants(final Expression exp) {
-        if (exp instanceof PropertyExpression) {
-            PropertyExpression pe = (PropertyExpression) exp;
-            if (pe.getObjectExpression() instanceof ClassExpression) {
-                ClassExpression ce = (ClassExpression) pe.getObjectExpression();
-                ClassNode type = ce.getType();
-                FieldNode field = ClassNodeUtils.getField(type, pe.getPropertyAsString());
-                if (type.isEnum() && field != null && field.isEnum()) return exp;
-                Expression constant = findConstant(field);
-                if (constant != null) return constant;
-            }
-        } else if (exp instanceof BinaryExpression) {
-            BinaryExpression be = (BinaryExpression) exp;
-            Expression lhs = transformInlineConstants(be.getLeftExpression());
-            Expression rhs = transformInlineConstants(be.getRightExpression());
-            if (be.getOperation().getType() == PLUS && lhs instanceof ConstantExpression && rhs instanceof ConstantExpression &&
-                    isStringType(lhs.getType()) && isStringType(rhs.getType())) {
-                // GROOVY-9855: inline string concat
-                return configure(be, new ConstantExpression(lhs.getText() + rhs.getText()));
-            }
-            be.setLeftExpression(lhs);
-            be.setRightExpression(rhs);
-            return be;
-        } else if (exp instanceof ListExpression) {
-            ListExpression origList = (ListExpression) exp;
-            ListExpression newList = new ListExpression();
-            boolean changed = false;
-            for (Expression e : origList.getExpressions()) {
-                Expression transformed = transformInlineConstants(e);
-                newList.addExpression(transformed);
-                if (transformed != e) changed = true;
-            }
-            if (changed) {
-                newList.setSourcePosition(origList);
-                return newList;
-            }
-            return origList;
-        }
-
-        return exp;
-    }
-
-    private static Expression findConstant(final FieldNode fn) {
-        if (fn != null && !fn.isEnum() && fn.isStatic() && fn.isFinal()
-                && fn.getInitialValueExpression() instanceof ConstantExpression) {
-            return fn.getInitialValueExpression();
-        }
-        return null;
-    }
+    //--------------------------------------------------------------------------
 
-    public static boolean isNullConstant(final Expression expression) {
-        return expression instanceof ConstantExpression && ((ConstantExpression) expression).isNullExpression();
-    }
-
-    public static boolean isThisExpression(final Expression expression) {
-        return expression instanceof VariableExpression && ((VariableExpression) expression).isThisExpression();
+    private static ConstantExpression configure(final Expression origX, final ConstantExpression newX) {
+        newX.setSourcePosition(origX);
+        return newX;
     }
 
-    public static boolean isSuperExpression(final Expression expression) {
-        return expression instanceof VariableExpression && ((VariableExpression) expression).isSuperExpression();
+    private static Number safeNumber(final ConstantExpression constX) {
+        Object value = constX.getValue();
+        return value instanceof Number ? (Number) value : null;
     }
 
-    public static boolean isThisOrSuper(final Expression expression) {
-        return isThisExpression(expression) || isSuperExpression(expression);
+    private ExpressionUtils() {
+        assert false;
     }
 }
diff --git a/src/test/gls/annotations/AnnotationTest.groovy b/src/test/gls/annotations/AnnotationTest.groovy
index 6597b25d78..b8885c8d46 100644
--- a/src/test/gls/annotations/AnnotationTest.groovy
+++ b/src/test/gls/annotations/AnnotationTest.groovy
@@ -18,51 +18,61 @@
  */
 package gls.annotations
 
-import gls.CompilableTestSupport
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
 
 /**
  * Tests various properties of annotation definitions.
  */
-final class AnnotationTest extends CompilableTestSupport {
+final class AnnotationTest {
+
+    private final GroovyShell shell = GroovyShell.withConfig {
+        imports {
+            star 'java.lang.annotation'
+            staticStar 'java.lang.annotation.ElementType'
+            staticStar 'java.lang.annotation.RetentionPolicy'
+        }
+    }
 
     /**
-     * Check that it is possible to annotate an annotation definition with field and method target elements.
+     * Checks that it is possible to annotate an annotation definition with field and method target elements.
      */
+    @Test
     void testAnnotateAnnotationDefinitionWithMethodAndFieldTargetElementTypes() {
-        shouldCompile """
-            import java.lang.annotation.*
-            import static java.lang.annotation.RetentionPolicy.*
-            import static java.lang.annotation.ElementType.*
-
+        shell.parse '''
             @Retention(RUNTIME)
             @Target([METHOD, FIELD])
-            @interface MyAnnotation { }
-        """
+            @interface MyAnnotation {
+            }
+        '''
     }
 
+    @Test
     void testCannotAnnotateAnnotationDefinitionIfTargetIsNotOfTypeOrAnnotationType() {
-        shouldNotCompile """
-            import java.lang.annotation.*
-            import static java.lang.annotation.ElementType.*
-
-            // all target elements except ANNOTATION_TYPE and TYPE
+        shouldFail shell, '''
+            // all targets except ANNOTATION_TYPE, TYPE, TYPE_PARAMETER, TYPE_USE and MODULE
             @Target([CONSTRUCTOR, METHOD, FIELD, LOCAL_VARIABLE, PACKAGE, PARAMETER])
-            @interface MyAnnotation { }
+            @interface AnAnnotation {
+            }
 
-            @MyAnnotation
-            @interface AnotherAnnotation {}
-        """
+            @AnAnnotation
+            @interface AnotherAnnotation {
+            }
+        '''
     }
 
     /**
-     * The @OneToMany cascade parameter takes an array of CascadeType.
+     * The {@code @OneToMany} cascade parameter takes an array of CascadeType.
      * To use this annotation in Java with this parameter, you do <code>@OneToMany(cascade = { CascadeType.ALL })</code>
      * In Groovy, you do <code>@OneToMany(cascade = [ CascadeType.ALL ])</code> (brackets instead of braces)
      * But when there's just one value in the array, the curly braces or brackets can be omitted:
      * <code>@OneToMany(cascade = [ CascadeType.ALL ])</code>
      */
+    @Test
     void testOmittingBracketsForSingleValueArrayParameter() {
-        shouldCompile """
+        shell.parse '''
             import gls.annotations.*
 
             class Book {}
@@ -76,28 +86,30 @@ final class AnnotationTest extends CompilableTestSupport {
 
             assert annotation instanceof OneToMany
             assert annotation.cascade() == [CascadeType.ALL]
-        """
+        '''
     }
 
+    @Test
     void testPrimitiveDefault() {
         // NOTE: for int anything else than a plain number will fail.
         // For example 1l will fail too, even if it could be converted.
         // If this should be changed, then further discussion is needed.
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 int x() default "1" // must be integer
             }
-        """
+        '''
 
-        shouldCompile """
+        shell.parse '''
             @interface X {
                 int x() default 1
             }
-        """
+        '''
     }
 
+    @Test
     void testConstant() {
-        assertScript """
+        assertScript shell, '''
             class Baz {
                 // static final int OTHER = 5
                 // below we would like to but can't use:
@@ -116,14 +128,12 @@ final class AnnotationTest extends CompilableTestSupport {
             }
 
             new Baz().run()
-        """
+        '''
     }
 
+    @Test // GROOVY-4811
     void testArrayDefault() {
-        // GROOVY-4811
-        assertScript '''
-            import java.lang.annotation.*
-
+        assertScript shell, '''
             @Retention(RetentionPolicy.RUNTIME)
             @Target(ElementType.TYPE)
             @interface Temp {
@@ -136,95 +146,94 @@ final class AnnotationTest extends CompilableTestSupport {
             assert Bar.getAnnotation(Temp).bar() == ['1']
         '''
 
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 String[] x() default ["1",2] // list must contain elements of correct type
             }
-        """
+        '''
 
-        shouldCompile """
+        shell.parse '''
             @interface X {
                 String[] x() default ["a","b"]
             }
-        """
+        '''
     }
 
+    @Test
     void testClassDefault() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 Class x() default "1" // must be list
             }
-        """
+        '''
 
-        shouldCompile """
+        shell.parse '''
             @interface X {
                 Class x() default Number.class // class with .class
             }
-        """
+        '''
 
-        shouldCompile """
+        shell.parse '''
             @interface X {
                 Class x() default Number
             }
-        """
+        '''
     }
 
+    @Test
     void testEnumDefault() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 ElementType x() default "1" // must be Enum
             }
-        """
+        '''
 
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 ElementType x() default Target.TYPE // must be Enum of correct type
             }
-        """
-
-        shouldCompile """
-            import java.lang.annotation.*
+        '''
 
+        shell.parse '''
             @interface X {
                 ElementType x() default ElementType.METHOD
             }
-        """
+        '''
     }
 
+    @Test
     void testAnnotationDefault() {
-        shouldNotCompile """
-            import java.lang.annotation.*
+        shouldFail shell, '''
             @interface X {
                 Target x() default "1" // must be Annotation
             }
-        """
-
-        shouldNotCompile """
-            import java.lang.annotation.*
+        '''
 
+        shouldFail shell, '''
             @interface X {
                 Target x() default @Retentention // must be correct type
             }
-        """
+        '''
 
-        shouldCompile """
-            import java.lang.annotation.*
+        shell.parse '''
             @interface X {
                 Target x() default @Target([ElementType.TYPE])
             }
-        """
+        '''
     }
 
+    @Test
     void testSelfReference() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 X x() default @X // self reference must not work
             }
-        """
+        '''
     }
 
+    @Test
     void testCyclicReference() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 Y x() default @Y
             }
@@ -232,52 +241,57 @@ final class AnnotationTest extends CompilableTestSupport {
             @interface Y {
                 X x() default @X
             }
-        """
+        '''
     }
 
+    @Test
     void testParameter() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 String x(int x) default "" // annotation members can't have parameters
             }
-        """
+        '''
     }
 
+    @Test
     void testThrowsClause() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 String x() throws IOException // annotation members can't have exceptions
             }
-        """
+        '''
     }
 
+    @Test
     void testExtends() {
-        shouldNotCompile """
-            @interface X extends Serializable{}
+        shouldFail shell, '''
             // annotation members can't extend
-        """
+            @interface X extends Serializable {
+            }
+        '''
     }
 
+    @Test
     void testInvalidMemberType() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 Object x() // Object is no type for constants
             }
-        """
+        '''
     }
 
+    @Test
     void testNull() {
-        shouldNotCompile """
+        shouldFail shell, '''
             @interface X {
                 String x() default null // null is no constant for annotations
             }
-        """
+        '''
     }
 
+    @Test
     void testUsage() {
-        assertScript """
-            import java.lang.annotation.*
-
+        assertScript shell, '''
             // a random annotation type
             @Retention(RetentionPolicy.RUNTIME)
             @interface MyAnnotation {
@@ -290,7 +304,6 @@ final class AnnotationTest extends CompilableTestSupport {
                 Target defaultAnnotation() default @Target([ElementType.TYPE])
             }
 
-
             @MyAnnotation(stringValue = "for class", intValue = 100)
             class Foo {}
 
@@ -305,13 +318,14 @@ final class AnnotationTest extends CompilableTestSupport {
 
             assert my.defaultEnum() == ElementType.TYPE
             assert my.defaultAnnotation() instanceof Target
-        """
+        '''
     }
 
+    @Test
     void testJavaAnnotationUsageWithGroovyKeyword() {
-        assertScript """
+        assertScript shell, '''
             package gls.annotations
-            import java.lang.annotation.*
+
             @JavaAnnotation(in = 3)
             class Foo {}
 
@@ -319,24 +333,23 @@ final class AnnotationTest extends CompilableTestSupport {
             assert annotations.size() == 1
             JavaAnnotation my = annotations[0]
             assert my.in() == 3
-        """
+        '''
     }
 
+    @Test
     void testUsageOnClass() {
-        assertScript """
-            import java.lang.annotation.*
-
+        assertScript shell, '''
             @Deprecated
             class Foo{}
 
             assert Foo.class.annotations.size() == 1
-        """
+        '''
     }
 
+    @Test
     void testFieldAndPropertyRuntimeRetention() {
-        assertScript """
+        assertScript shell, '''
             import org.codehaus.groovy.ast.ClassNode
-            import java.lang.annotation.*
 
             @Retention(RetentionPolicy.RUNTIME)
             @interface Annotation1 {}
@@ -359,13 +372,12 @@ final class AnnotationTest extends CompilableTestSupport {
                     assert annotations: "Annotation on 'property1' not found"
                 }
             }
-        """
+        '''
     }
 
+    @Test
     void testSingletonArrayUsage() {
-        assertScript """
-            import java.lang.annotation.*
-
+        assertScript shell, '''
             // a random annnotation type
             @Retention(RetentionPolicy.RUNTIME)
             @interface MyAnnotation {
@@ -380,29 +392,31 @@ final class AnnotationTest extends CompilableTestSupport {
             MyAnnotation my = annotations[0]
             assert my.things().size() == 1
             assert my.things()[0] == "x"
-        """
+        '''
     }
 
+    @Test
     void testSettingAnnotationMemberTwice() {
-        shouldNotCompile """
-        package gls.annotations
+        shouldFail shell, '''
+            package gls.annotations
 
-        @JavaAnnotation(in = 1, in = 2)
-        class Foo {}
-    """
+            @JavaAnnotation(in = 1, in = 2)
+            class Foo {}
+        '''
     }
 
+    @Test
     void testGetterCallWithSingletonAnnotation() {
-        assertScript """
+        assertScript shell, '''
             @Singleton class MyService3410{}
             assert MyService3410.instance != null
             assert MyService3410.getInstance() != null
-      """
+        '''
     }
 
-    void testAttributePropertyConstants() {
-        assertScript """
-            import java.lang.annotation.*
+    @Test
+    void testAttributeValueConstants() {
+        assertScript shell, '''
             import static Constants.*
 
             @Retention(RetentionPolicy.RUNTIME)
@@ -434,12 +448,12 @@ final class AnnotationTest extends CompilableTestSupport {
             assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedStrings').annotations[0].array() == ['foo', 'bar', "GROOVY"]
             assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedMath1').annotations[0].value() == Math.PI
             assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedMath2').annotations[0].value() == Constants.APPROX_PI
-        """
+        '''
     }
 
-    void testNestedAttributePropertyConstants() {
-        assertScript """
-            import java.lang.annotation.*
+    @Test
+    void testAttributeValueConstants2() {
+        assertScript shell, '''
             import static Constants.*
 
             @Retention(RetentionPolicy.RUNTIME)
@@ -473,13 +487,126 @@ final class AnnotationTest extends CompilableTestSupport {
 
             assert ClassWithNestedAnnotationUsingConstant.getDeclaredField('outer').annotations[0].array() == ['foo', 'GROOVY', 'baz']
             assert ClassWithNestedAnnotationUsingConstant.getDeclaredField('outer').annotations[0].value().value() == ['foobar', 'bar', 'GROOVY']
-        """
+        '''
     }
 
-    void testRuntimeRetentionAtAllLevels() {
-        assertScript """
-            import java.lang.annotation.*
+    @Test // GROOVY-3278
+    void testAttributeValueConstants3() {
+        assertScript shell, '''
+            import gls.annotations.*
+
+            @ConstAnnotation(ints = 42)
+            class Child1 extends Base3278 {}
+
+            class OtherConstants {
+                static final Integer CONST3 = 3278
+            }
+
+            @ConstAnnotation(ints = [-1, Base3278.CONST, Base3278.CONST1, Base3278.CONST2, OtherConstants.CONST3, Integer.MIN_VALUE],
+                          strings = ['foo', Base3278.CONST4, Base3278.CONST5, Base3278.CONST5 + 'bing'])
+            class Child3 extends Base3278 {}
+
+            assert new Child1().ints() == [42]
+            assert new Child2().ints() == [2147483647]
+            new Child3().with {
+                assert ints() == [-1, 3278, 2048, 2070, 3278, -2147483648]
+                assert strings() == ['foo', 'foobar', 'foobarbaz', 'foobarbazbing']
+            }
+        '''
+    }
+
+    @Test // GROOVY-8898
+    void testAttributeValueConstants4() {
+        assertScript shell, '''
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.TYPE)
+            @interface MyAnnotation {
+                String[] groups() default []
+                MyEnum alt() default MyEnum.ALT1
+            }
+
+            class Base {
+                static final String CONST = 'bar'
+                def groups() { getClass().annotations[0].groups() }
+                def alt() { getClass().annotations[0].alt() }
+            }
+
+            enum MyEnum {
+                ALT1, ALT2;
+                public static final String CONST = 'baz'
+            }
+
+            @MyAnnotation(groups = ['foo', Base.CONST, MyEnum.CONST], alt = MyEnum.ALT2)
+            class Child extends Base {}
+
+            new Child().with {
+                assert groups() == ['foo', 'bar', 'baz']
+                assert alt() == MyEnum.ALT2
+            }
+        '''
+    }
+
+    @Test
+    void testAttributeValueConstants5() {
+        assertScript shell, '''
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.FIELD)
+            @interface Pattern {
+                String regexp()
+            }
+
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.FIELD)
+            @interface LimitedDouble {
+                double max() default Double.MAX_VALUE
+                double min() default Double.MIN_VALUE
+                double zero() default (1.0 - 1.0d) * 1L
+            }
+
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.FIELD)
+            @interface IntegerConst {
+                int kilo() default 0b1 << 10
+                int two() default 64 >> 5
+                int nine() default 0b1100 ^ 0b101
+                int answer() default 42
+            }
+
+            class MyClass {
+                private static interface Constants {
+                    static final String CONST = 'foo' + 'bar'
+                }
+
+                @Pattern(regexp=Constants.CONST)
+                String myString
+
+                @LimitedDouble(min=0.0d, max=50.0d * 2)
+                double myDouble
+
+                @IntegerConst(answer=(5 ** 2) * (2 << 1))
+                int myInteger
+            }
+
+            assert MyClass.getDeclaredField('myString').annotations[0].regexp() == 'foobar'
+
+            MyClass.getDeclaredField('myDouble').annotations[0].with {
+                assert max() == 100.0d
+                assert min() == 0.0d
+                assert zero() == 0.0d
+            }
 
+            MyClass.getDeclaredField('myInteger').annotations[0].with {
+                assert kilo() == 1024
+                assert two() == 2
+                assert nine() == 9
+                assert answer() == 100
+            }
+        '''
+    }
+
+    @Test
+    void testRuntimeRetentionAtAllLevels() {
+        assertScript shell, '''
             @Retention(RetentionPolicy.RUNTIME)
             @Target([ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.FIELD])
             @interface MyAnnotation {
@@ -511,15 +638,13 @@ final class AnnotationTest extends CompilableTestSupport {
             def constructor = c1.class.constructors[0]
             assert constructor.annotations[0].value() == 'constructor'
             assert constructor.parameterAnnotations[0][0].value() == 'constructor param'
-        """
+        '''
     }
 
+    @Test
     void testAnnotationWithValuesNotGivenForAttributesWithoutDefaults() {
-        def scriptStr
-        scriptStr = """
-            import java.lang.annotation.Retention
-            import java.lang.annotation.RetentionPolicy
-
+        // compiler should not allow this, because there is no default value for x
+        shouldFail shell, '''
             @Retention(RetentionPolicy.RUNTIME)
             @interface Annot3454V1 {
                 String x()
@@ -527,15 +652,10 @@ final class AnnotationTest extends CompilableTestSupport {
 
             @Annot3454V1
             class Bar {}
-        """
-
-        // compiler should not allow this, because there is no default value for x
-        shouldNotCompile(scriptStr)
-
-        scriptStr = """
-            import java.lang.annotation.Retention
-            import java.lang.annotation.RetentionPolicy
+        '''
 
+        // compiler should not allow this, because there is no default value for y
+        shouldFail shell, '''
             @Retention(RetentionPolicy.RUNTIME)
             @interface Annot3454V2 {
                 String x() default 'xxx'
@@ -544,15 +664,10 @@ final class AnnotationTest extends CompilableTestSupport {
 
             @Annot3454V2
             class Bar {}
-        """
-
-        // compiler should not allow this, because there is no default value for y
-        shouldNotCompile(scriptStr)
-
-        scriptStr = """
-            import java.lang.annotation.Retention
-            import java.lang.annotation.RetentionPolicy
+        '''
 
+        // compiler should allow this, because there is default value for x and value provided for y
+        assertScript shell, '''
             @Retention(RetentionPolicy.RUNTIME)
             @interface Annot3454V3 {
                 String x() default 'xxx'
@@ -565,55 +680,53 @@ final class AnnotationTest extends CompilableTestSupport {
             def anno = Bar.class.getAnnotation(Annot3454V3)
             assert anno.x() == 'xxx'
             assert anno.y() == 'yyy'
-        """
-        // compiler should allow this, because there is default value for x and value provided for y
-        assertScript(scriptStr)
+        '''
     }
 
+    @Test
     void testAllowedTargetsCheckAlsoWorksForAnnotationTypesDefinedOutsideThisCompilationUnit() {
-        shouldCompile """
-            @java.lang.annotation.Documented
-            @interface Foo {}
-        """
+        shell.parse '''
+            @Documented
+            @interface Foo {
+            }
+        '''
 
-        shouldNotCompile """
-            @java.lang.annotation.Documented
+        shouldFail shell, '''
+            @Documented
             class Foo {
                 def bar
             }
-        """
+        '''
 
-        shouldNotCompile """
+        shouldFail shell, '''
             class Foo {
-                @java.lang.annotation.Documented
+                @Documented
                 def bar
             }
-        """
+        '''
 
-        shouldCompile """
+        shell.parse '''
             import org.junit.*
 
             class Foo {
                 @Test
                 void foo() {}
             }
-        """
+        '''
 
-        shouldNotCompile """
+        shouldFail shell, '''
             import org.junit.*
 
             @Test
             class Foo {
                 void foo() {}
             }
-        """
+        '''
     }
 
-    // GROOVY-6025
+    @Test // GROOVY-6025
     void testAnnotationDefinitionDefaultValues() {
-        assertScript '''
-            import java.lang.annotation.*
-
+        assertScript shell, '''
             @Retention(RetentionPolicy.RUNTIME)
             @Target([ElementType.TYPE, ElementType.METHOD])
             public @interface Foo {
@@ -644,49 +757,62 @@ final class AnnotationTest extends CompilableTestSupport {
                    string == "b=6|c1='A'|c2='B'|c3='C'|c4='D'|d1=2.0|d2=2.1|d3=2.2|d4=2.3|f1=1.0f|f2=1.1f|f3=1.2f|f4=1.3f|i1=0|i2=1|s1=2|s2=3|s3=4|s4=5" ||
                    // changed in some jdk14 versions
                    string == "b=(byte)0x06|c1='A'|c2='B'|c3='C'|c4='D'|d1=2.0|d2=2.1|d3=2.2|d4=2.3|f1=1.0f|f2=1.1f|f3=1.2f|f4=1.3f|i1=0|i2=1|s1=2|s2=3|s3=4|s4=5"
-
         '''
     }
 
-    // GROOVY-6093
+    @Test // GROOVY-6093
     void testAnnotationOnEnumConstant() {
-        assertScript '''import gls.annotations.XmlEnum
+        assertScript shell, '''
+            import gls.annotations.XmlEnum
             import gls.annotations.XmlEnumValue
 
             @XmlEnum
             enum GroovyEnum {
-                @XmlEnumValue("good")
-                BAD
+                @XmlEnumValue('good') BAD
             }
+
             assert GroovyEnum.class.getField('BAD').isAnnotationPresent(XmlEnumValue)
         '''
     }
 
-    // GROOVY-7151
+    @Test // GROOVY-7151
     void testAnnotateAnnotationDefinitionWithAnnotationWithTypeTarget() {
-        shouldCompile codeWithMetaAnnotationWithTarget("TYPE")
+        shell.parse codeWithMetaAnnotationWithTarget('TYPE')
     }
 
     void testAnnotateAnnotationDefinitionWithAnnotationWithAnnotationTypeTarget() {
-        shouldCompile codeWithMetaAnnotationWithTarget("ANNOTATION_TYPE")
+        shell.parse codeWithMetaAnnotationWithTarget('ANNOTATION_TYPE')
+    }
+
+    private static String codeWithMetaAnnotationWithTarget(targetElementTypeName) {
+        return """
+            import java.lang.annotation.*
+            import static java.lang.annotation.RetentionPolicy.*
+            import static java.lang.annotation.ElementType.*
+
+            @Retention(RUNTIME)
+            @Target(${targetElementTypeName})
+            @interface Import {}
+
+            @Retention(RUNTIME)
+            @Target([FIELD])
+            @Import
+            @interface EnableFeature { }
+        """
     }
 
-    // GROOVY-7806
+    @Test // GROOVY-7806
     void testNewlinesAllowedBeforeBlock() {
-        shouldCompile '''
+        shell.parse '''
             @interface ANNOTATION_A
             {
             }
         '''
     }
 
-    // GROOVY-8226
+    @Test // GROOVY-8226
     void testAnnotationOnParameterType() {
-        assertScript '''
-            import java.lang.annotation.*
-            import static java.lang.annotation.ElementType.*
-            import static java.lang.annotation.RetentionPolicy.*
-
+        assertScript shell, '''
             @Target([PARAMETER, FIELD, METHOD, ANNOTATION_TYPE, TYPE_USE])
             @Retention(RUNTIME)
             public @interface NonNull { }
@@ -706,11 +832,9 @@ final class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
-    // GROOVY-8234
+    @Test // GROOVY-8234
     void testAnnotationWithRepeatableSupported() {
-        assertScript '''
-            import java.lang.annotation.*
-
+        assertScript shell, '''
             class MyClass {
                 // TODO confirm the JDK9 behavior is what we expect
                 private static final List<String> expected = [
@@ -766,11 +890,9 @@ final class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
-    // GROOVY-9452
+    @Test // GROOVY-9452
     void testDuplicationAnnotationOnClassWithParams() {
-        def err = shouldFail '''
-            import java.lang.annotation.*
-
+        def err = shouldFail shell, '''
             @Target(ElementType.TYPE)
             @Retention(RetentionPolicy.RUNTIME)
             @Repeatable(As)
@@ -792,9 +914,9 @@ final class AnnotationTest extends CompilableTestSupport {
         assert err =~ /Cannot specify duplicate annotation/
     }
 
-    // GROOVY-7033
+    @Test // GROOVY-7033
     void testClassLiteralsRecognizedForAnonymousInnerClassAnnotationUsage() {
-        shouldCompile '''
+        shell.parse '''
             @interface A {
                 Class value()
             }
@@ -809,8 +931,9 @@ final class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
+    @Test
     void testVariableExpressionsReferencingConstantsSeenForAnnotationAttributes() {
-        shouldCompile '''
+        shell.parse '''
             class C {
                 public static final String VALUE = 'rawtypes'
                 @SuppressWarnings(VALUE)
@@ -819,125 +942,9 @@ final class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
-    void testSimpleBinaryExpressions() {
-        assertScript '''
-            import java.lang.annotation.*
-
-            @Retention(RetentionPolicy.RUNTIME)
-            @Target(ElementType.FIELD)
-            @interface Pattern {
-                String regexp()
-            }
-
-            @Retention(RetentionPolicy.RUNTIME)
-            @Target(ElementType.FIELD)
-            @interface LimitedDouble {
-                double max() default Double.MAX_VALUE
-                double min() default Double.MIN_VALUE
-                double zero() default (1.0 - 1.0d) * 1L
-            }
-
-            @Retention(RetentionPolicy.RUNTIME)
-            @Target(ElementType.FIELD)
-            @interface IntegerConst {
-                int kilo() default 0b1 << 10
-                int two() default 64 >> 5
-                int nine() default 0b1100 ^ 0b101
-                int answer() default 42
-            }
-
-            class MyClass {
-                private static interface Constants {
-                    static final String CONST = 'foo' + 'bar'
-                }
-
-                @Pattern(regexp=Constants.CONST)
-                String myString
-
-                @LimitedDouble(min=0.0d, max=50.0d * 2)
-                double myDouble
-
-                @IntegerConst(answer=(5 ** 2) * (2 << 1))
-                int myInteger
-            }
-
-            assert MyClass.getDeclaredField('myString').annotations[0].regexp() == 'foobar'
-
-            MyClass.getDeclaredField('myDouble').annotations[0].with {
-                assert max() == 100.0d
-                assert min() == 0.0d
-                assert zero() == 0.0d
-            }
-
-            MyClass.getDeclaredField('myInteger').annotations[0].with {
-                assert kilo() == 1024
-                assert two() == 2
-                assert nine() == 9
-                assert answer() == 100
-            }
-        '''
-    }
-
-    void testAnnotationAttributeConstantFromPrecompiledGroovyClass() {
-        // GROOVY-3278
-        assertScript '''
-            import gls.annotations.*
-
-            @ConstAnnotation(ints = 42)
-            class Child1 extends Base3278 {}
-
-            class OtherConstants {
-                static final Integer CONST3 = 3278
-            }
-
-            @ConstAnnotation(ints = [-1, Base3278.CONST, Base3278.CONST1, Base3278.CONST2, OtherConstants.CONST3, Integer.MIN_VALUE],
-                          strings = ['foo', Base3278.CONST4, Base3278.CONST5, Base3278.CONST5 + 'bing'])
-            class Child3 extends Base3278 {}
-
-            assert new Child1().ints() == [42]
-            assert new Child2().ints() == [2147483647]
-            new Child3().with {
-                assert ints() == [-1, 3278, 2048, 2070, 3278, -2147483648]
-                assert strings() == ['foo', 'foobar', 'foobarbaz', 'foobarbazbing']
-            }
-        '''
-    }
-
-    void testAnnotationAttributeConstantFromEnumConstantField() {
-        // GROOVY-8898
-        assertScript '''
-            import java.lang.annotation.*
-
-            @Retention(RetentionPolicy.RUNTIME)
-            @Target(ElementType.TYPE)
-            @interface MyAnnotation {
-                String[] groups() default []
-                MyEnum alt() default MyEnum.ALT1
-            }
-
-            class Base {
-                static final String CONST = 'bar'
-                def groups() { getClass().annotations[0].groups() }
-                def alt() { getClass().annotations[0].alt() }
-            }
-
-            enum MyEnum {
-                ALT1, ALT2;
-                public static final String CONST = 'baz'
-            }
-
-            @MyAnnotation(groups = ['foo', Base.CONST, MyEnum.CONST], alt = MyEnum.ALT2)
-            class Child extends Base {}
-
-            new Child().with {
-                assert groups() == ['foo', 'bar', 'baz']
-                assert alt() == MyEnum.ALT2
-            }
-        '''
-    }
-
+    @Test
     void testAnnotationRetentionMirrorsJava() {
-        assertScript '''
+        assertScript shell, '''
             for (retention in ['', '@Retention(SOURCE)', '@Retention(CLASS)', '@Retention(RUNTIME)']) {
                 def src = """
                     import java.lang.annotation.Retention;
@@ -952,9 +959,9 @@ final class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
+    @Test
     void testAnnotationWithRepeatableSupportedPrecompiledJava() {
-        assertScript '''
-            import java.lang.annotation.*
+        assertScript shell, '''
             import gls.annotations.*
 
             class MyClass {
@@ -991,22 +998,4 @@ final class AnnotationTest extends CompilableTestSupport {
             }
         '''
     }
-
-    //Parametrized tests in Spock would allow to make it much more readable
-    private static String codeWithMetaAnnotationWithTarget(String targetElementTypeName) {
-        """
-            import java.lang.annotation.*
-            import static java.lang.annotation.RetentionPolicy.*
-            import static java.lang.annotation.ElementType.*
-
-            @Retention(RUNTIME)
-            @Target(${targetElementTypeName})
-            @interface Import {}
-
-            @Retention(RUNTIME)
-            @Target([FIELD])
-            @Import
-            @interface EnableFeature { }
-        """
-    }
 }